diff --git a/CMakeLists.txt b/CMakeLists.txt index 638db439a6..02d69f9d6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ createBuildInfo() add_subdirectory(libdevcore) add_subdirectory(libethash) +add_subdirectory(libhwmon) if (ETHASHCL) add_subdirectory(libethash-cl) endif () diff --git a/ethminer/MinerAux.h b/ethminer/MinerAux.h index e525a86842..0cb97a7417 100644 --- a/ethminer/MinerAux.h +++ b/ethminer/MinerAux.h @@ -244,6 +244,10 @@ class MinerCLI { m_report_stratum_hashrate = true; } + else if ((arg == "-HWMON") && i + 1 < argc) + { + m_show_hwmonitors = true; + } #endif #if API_CORE @@ -572,6 +576,7 @@ class MinerCLI << " 1: eth-proxy compatible: dwarfpool, f2pool, nanopool (required for hashrate reporting to work with nanopool)" << endl << " 2: EthereumStratum/1.0.0: nicehash" << endl << " -RH, --report-hashrate Report current hashrate to pool (please only enable on pools supporting this)" << endl + << " -HWMON Displays gpu temp and fan percent." << endl << " -SE, --stratum-email <s> Email address used in eth-proxy (optional)" << endl << " --farm-recheck <n> Leave n ms between checks for changed work (default: 500). When using stratum, use a high value (i.e. 2000) to get more stable hashrate output" << endl #endif @@ -813,7 +818,7 @@ class MinerCLI }); for (unsigned i = 0; !completed; ++i) { - auto mp = f.miningProgress(); + auto mp = f.miningProgress(m_show_hwmonitors); if (current) { minelog << mp << f.getSolutionStats() << f.farmLaunchedFormatted(); @@ -962,7 +967,7 @@ class MinerCLI while (client.isRunning()) { - auto mp = f.miningProgress(); + auto mp = f.miningProgress(m_show_hwmonitors); if (client.isConnected()) { if (client.current()) @@ -1011,7 +1016,7 @@ class MinerCLI while (client.isRunning()) { - auto mp = f.miningProgress(); + auto mp = f.miningProgress(m_show_hwmonitors); if (client.isConnected()) { if (client.current()) @@ -1084,6 +1089,7 @@ class MinerCLI unsigned m_defaultStratumFarmRecheckPeriod = 2000; bool m_farmRecheckSet = false; int m_worktimeout = 180; + bool m_show_hwmonitors = false; #if API_CORE int m_api_port = 0; #endif diff --git a/libapicore/ApiServer.cpp b/libapicore/ApiServer.cpp index 56286fb884..aa3587efb8 100644 --- a/libapicore/ApiServer.cpp +++ b/libapicore/ApiServer.cpp @@ -17,7 +17,7 @@ void ApiServer::getMinerStat1(const Json::Value& request, Json::Value& response) auto runningTime = std::chrono::duration_cast<std::chrono::minutes>(steady_clock::now() - this->m_farm.farmLaunched()); SolutionStats s = this->m_farm.getSolutionStats(); - WorkingProgress p = this->m_farm.miningProgress(); + WorkingProgress p = this->m_farm.miningProgress(true); ostringstream totalMhEth; ostringstream totalMhDcr; @@ -37,7 +37,14 @@ void ApiServer::getMinerStat1(const Json::Value& request, Json::Value& response) { detailedMhEth << std::fixed << std::setprecision(0) << (p.minerRate(i) / 1000.0f) << (((numGpus -1) > gpuIndex) ? ";" : ""); detailedMhDcr << "off" << (((numGpus -1) > gpuIndex) ? ";" : ""); // DualMining not supported - tempAndFans << "50;50" <<(((numGpus -1) > gpuIndex) ? ";" : ""); // Fetching Temp and Fans not supported + gpuIndex++; + } + + gpuIndex = 0; + numGpus = p.minerMonitors.size(); + for (auto const& i : p.minerMonitors) + { + tempAndFans << i.tempC << ";" << i.fanP << (((numGpus - 1) > gpuIndex) ? "; " : ""); // Fetching Temp and Fans gpuIndex++; } diff --git a/libethash-cl/CLMiner.cpp b/libethash-cl/CLMiner.cpp index acb1559c6c..839b02b1b4 100644 --- a/libethash-cl/CLMiner.cpp +++ b/libethash-cl/CLMiner.cpp @@ -340,6 +340,28 @@ bool CLMiner::configureGPU( return false; } +HwMonitor CLMiner::hwmon() +{ + HwMonitor hw; + unsigned int tempC = 0, fanpcnt = 0; + if (nvmlh) { + wrap_nvml_get_tempC(nvmlh, index, &tempC); + wrap_nvml_get_fanpcnt(nvmlh, index, &fanpcnt); + } + if (adlh) { + wrap_adl_get_tempC(adlh, index, &tempC); + wrap_adl_get_fanpcnt(adlh, index, &fanpcnt); + } +#if defined(__linux) + if (sysfsh) { + wrap_amdsysfs_get_tempC(sysfsh, index, &tempC); + wrap_amdsysfs_get_fanpcnt(sysfsh, index, &fanpcnt); + } +#endif + hw.tempC = tempC; + hw.fanP = fanpcnt; + return hw; +} bool CLMiner::init(const h256& seed) { @@ -362,10 +384,15 @@ bool CLMiner::init(const h256& seed) if (platformName == "NVIDIA CUDA") { platformId = OPENCL_PLATFORM_NVIDIA; + nvmlh = wrap_nvml_create(); } else if (platformName == "AMD Accelerated Parallel Processing") { platformId = OPENCL_PLATFORM_AMD; + adlh = wrap_adl_create(); +#if defined(__linux) + sysfsh = wrap_amdsysfs_create(); +#endif } else if (platformName == "Clover") { diff --git a/libethash-cl/CLMiner.h b/libethash-cl/CLMiner.h index 8c6ab30538..27064a947b 100644 --- a/libethash-cl/CLMiner.h +++ b/libethash-cl/CLMiner.h @@ -8,6 +8,11 @@ #include <libdevcore/Worker.h> #include <libethcore/EthashAux.h> #include <libethcore/Miner.h> +#include <libhwmon/wrapnvml.h> +#include <libhwmon/wrapadl.h> +#if defined(__linux) +#include <libhwmon/wrapamdsysfs.h> +#endif #define CL_USE_DEPRECATED_OPENCL_1_2_APIS true #define CL_HPP_ENABLE_EXCEPTIONS true @@ -68,7 +73,7 @@ class CLMiner: public Miner s_devices[i] = _devices[i]; } } - + HwMonitor hwmon() override; protected: void kickOff() override; void pause() override; @@ -100,6 +105,11 @@ class CLMiner: public Miner /// The initial global work size for the searches static unsigned s_initialGlobalWorkSize; + wrap_nvml_handle *nvmlh = NULL; + wrap_adl_handle *adlh = NULL; +#if defined(__linux) + wrap_amdsysfs_handle *sysfsh = NULL; +#endif }; } diff --git a/libethash-cl/CMakeLists.txt b/libethash-cl/CMakeLists.txt index 91f974d3c4..55e58e9215 100644 --- a/libethash-cl/CMakeLists.txt +++ b/libethash-cl/CMakeLists.txt @@ -30,5 +30,5 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(..) add_library(ethash-cl ${SOURCES}) -target_link_libraries(ethash-cl PUBLIC ethcore ethash) +target_link_libraries(ethash-cl PUBLIC ethcore ethash hwmon) target_link_libraries(ethash-cl PRIVATE OpenCL::OpenCL) diff --git a/libethash-cuda/CMakeLists.txt b/libethash-cuda/CMakeLists.txt index 0e42953f70..3fd6d91b1f 100644 --- a/libethash-cuda/CMakeLists.txt +++ b/libethash-cuda/CMakeLists.txt @@ -25,7 +25,7 @@ file(GLOB sources "*.cpp" "*.cu") file(GLOB headers "*.h" "*.cuh") cuda_add_library(ethash-cuda STATIC ${sources} ${headers}) -target_link_libraries(ethash-cuda ethcore ethash) +target_link_libraries(ethash-cuda ethcore ethash hwmon) target_include_directories(ethash-cuda PUBLIC ${CUDA_INCLUDE_DIRS}) target_include_directories(ethash-cuda PRIVATE .. ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/libethash-cuda/CUDAMiner.cpp b/libethash-cuda/CUDAMiner.cpp index f870615999..d32c695238 100644 --- a/libethash-cuda/CUDAMiner.cpp +++ b/libethash-cuda/CUDAMiner.cpp @@ -213,6 +213,15 @@ void CUDAMiner::listDevices() return ethash_cuda_miner::listDevices(); } +HwMonitor CUDAMiner::hwmon() +{ + HwMonitor hw; + if (m_miner) { + hw = m_miner->hwmon(); + } + return hw; +} + bool CUDAMiner::configureGPU( unsigned _blockSize, unsigned _gridSize, diff --git a/libethash-cuda/CUDAMiner.h b/libethash-cuda/CUDAMiner.h index 82226cc2a3..690a3ad47f 100644 --- a/libethash-cuda/CUDAMiner.h +++ b/libethash-cuda/CUDAMiner.h @@ -69,10 +69,10 @@ class EthashCUDAHook; s_devices[i] = _devices[i]; } } + HwMonitor hwmon() override; protected: void kickOff() override; void pause() override; - private: void workLoop() override; void report(uint64_t _nonce); diff --git a/libethash-cuda/ethash_cuda_miner.cpp b/libethash-cuda/ethash_cuda_miner.cpp index d2d7fe45b2..ea2a0c6aed 100644 --- a/libethash-cuda/ethash_cuda_miner.cpp +++ b/libethash-cuda/ethash_cuda_miner.cpp @@ -211,14 +211,16 @@ bool ethash_cuda_miner::init(ethash_light_t _light, uint8_t const* _lightData, u return false; // use selected device - int device_num = std::min<int>((int)_deviceId, device_count - 1); + m_device_num = std::min<int>((int)_deviceId, device_count - 1); + + nvmlh = wrap_nvml_create(); cudaDeviceProp device_props; - CUDA_SAFE_CALL(cudaGetDeviceProperties(&device_props, device_num)); + CUDA_SAFE_CALL(cudaGetDeviceProperties(&device_props, m_device_num)); cudalog << "Using device: " << device_props.name << " (Compute " + to_string(device_props.major) + "." + to_string(device_props.minor) + ")"; - CUDA_SAFE_CALL(cudaSetDevice(device_num)); + CUDA_SAFE_CALL(cudaSetDevice(m_device_num)); CUDA_SAFE_CALL(cudaDeviceReset()); CUDA_SAFE_CALL(cudaSetDeviceFlags(s_scheduleFlag)); CUDA_SAFE_CALL(cudaDeviceSetCacheConfig(cudaFuncCachePreferL1)); @@ -260,13 +262,13 @@ bool ethash_cuda_miner::init(ethash_light_t _light, uint8_t const* _lightData, u if (!*hostDAG) { - cudalog << "Generating DAG for GPU #" << device_num; - ethash_generate_dag(dagSize, s_gridSize, s_blockSize, m_streams[0], device_num); + cudalog << "Generating DAG for GPU #" << m_device_num; + ethash_generate_dag(dagSize, s_gridSize, s_blockSize, m_streams[0], m_device_num); if (_cpyToHost) { uint8_t* memoryDAG = new uint8_t[dagSize]; - cudalog << "Copying DAG from GPU #" << device_num << " to host"; + cudalog << "Copying DAG from GPU #" << m_device_num << " to host"; CUDA_SAFE_CALL(cudaMemcpy(reinterpret_cast<void*>(memoryDAG), dag, dagSize, cudaMemcpyDeviceToHost)); *hostDAG = (void*)memoryDAG; @@ -274,7 +276,7 @@ bool ethash_cuda_miner::init(ethash_light_t _light, uint8_t const* _lightData, u } else { - cudalog << "Copying DAG from host to GPU #" << device_num; + cudalog << "Copying DAG from host to GPU #" << m_device_num; const void* hdag = (const void*)(*hostDAG); CUDA_SAFE_CALL(cudaMemcpy(reinterpret_cast<void*>(dag), hdag, dagSize, cudaMemcpyHostToDevice)); } @@ -359,3 +361,16 @@ void ethash_cuda_miner::search(uint8_t const* header, uint64_t target, search_ho } } +dev::eth::HwMonitor ethash_cuda_miner::hwmon() +{ + dev::eth::HwMonitor hw; + if (nvmlh) { + unsigned int tempC = 0, fanpcnt = 0; + wrap_nvml_get_tempC(nvmlh, nvmlh->cuda_nvml_device_id[m_device_num], &tempC); + wrap_nvml_get_fanpcnt(nvmlh, nvmlh->cuda_nvml_device_id[m_device_num], &fanpcnt); + hw.tempC = tempC; + hw.fanP = fanpcnt; + } + return hw; +} + diff --git a/libethash-cuda/ethash_cuda_miner.h b/libethash-cuda/ethash_cuda_miner.h index 65134cff43..9086f05c63 100644 --- a/libethash-cuda/ethash_cuda_miner.h +++ b/libethash-cuda/ethash_cuda_miner.h @@ -5,6 +5,8 @@ #include <time.h> #include <functional> #include <libethash/ethash.h> +#include <libethcore/Miner.h> +#include <libhwmon/wrapnvml.h> #include "ethash_cuda_miner_kernel.h" class ethash_cuda_miner @@ -39,6 +41,7 @@ class ethash_cuda_miner void finish(); void search(uint8_t const* header, uint64_t target, search_hook& hook, bool _ethStratum, uint64_t _startN); + dev::eth::HwMonitor hwmon(); /* -- default values -- */ /// Default value of the block size. Also known as workgroup size. @@ -54,8 +57,8 @@ class ethash_cuda_miner uint64_t m_current_nonce; uint64_t m_starting_nonce; uint64_t m_current_index; - uint32_t m_sharedBytes; + int m_device_num; volatile uint32_t ** m_search_buf; cudaStream_t * m_streams; @@ -70,4 +73,6 @@ class ethash_cuda_miner static unsigned s_scheduleFlag; static unsigned m_parallelHash; + + wrap_nvml_handle *nvmlh = NULL; }; diff --git a/libethcore/Farm.h b/libethcore/Farm.h index 28c9bc014c..8c5af85c80 100644 --- a/libethcore/Farm.h +++ b/libethcore/Farm.h @@ -217,7 +217,7 @@ class Farm: public FarmFace * @brief Get information on the progress of mining this work package. * @return The progress with mining so far. */ - WorkingProgress const& miningProgress() const + WorkingProgress const& miningProgress(bool hwmon = false) const { WorkingProgress p; p.ms = 0; @@ -225,8 +225,9 @@ class Farm: public FarmFace { Guard l2(x_minerWork); for (auto const& i : m_miners) { - (void) i; // unused p.minersHashes.push_back(0); + if (hwmon) + p.minerMonitors.push_back(i->hwmon()); } } @@ -327,6 +328,8 @@ class Farm: public FarmFace mutable Mutex x_progress; mutable WorkingProgress m_progress; + mutable Mutex x_hwmons; + SolutionFound m_onSolutionFound; MinerRestart m_onMinerRestart; diff --git a/libethcore/Miner.h b/libethcore/Miner.h index e2942b0d63..70b63955ee 100644 --- a/libethcore/Miner.h +++ b/libethcore/Miner.h @@ -64,6 +64,17 @@ enum class MinerType CUDA }; +struct HwMonitor +{ + int tempC = 0; + int fanP = 0; +}; + +inline std::ostream& operator<<(std::ostream& os, HwMonitor _hw) +{ + return os << _hw.tempC << "C " << _hw.fanP << "%"; +} + /// Describes the progress of a mining operation. struct WorkingProgress { @@ -72,6 +83,7 @@ struct WorkingProgress uint64_t rate() const { return ms == 0 ? 0 : hashes * 1000 / ms; } std::vector<uint64_t> minersHashes; + std::vector<HwMonitor> minerMonitors; uint64_t minerRate(const uint64_t hashCount) const { return ms == 0 ? 0 : hashCount * 1000 / ms; } }; @@ -85,7 +97,10 @@ inline std::ostream& operator<<(std::ostream& _out, WorkingProgress _p) for (size_t i = 0; i < _p.minersHashes.size(); ++i) { mh = _p.minerRate(_p.minersHashes[i]) / 1000000.0f; - _out << "gpu/" << i << " " << EthTeal << std::fixed << std::setw(5) << std::setprecision(2) << mh << EthReset << " "; + _out << "gpu/" << i << " " << EthTeal << std::fixed << std::setw(5) << std::setprecision(2) << mh << EthReset; + if (_p.minerMonitors.size() == _p.minersHashes.size()) + _out << " " << EthTeal << _p.minerMonitors[i] << EthReset; + _out << " "; } return _out; @@ -174,6 +189,8 @@ class Miner: public Worker void resetHashCount() { m_hashCount = 0; } + virtual HwMonitor hwmon() = 0; + protected: /** diff --git a/libhwmon/CMakeLists.txt b/libhwmon/CMakeLists.txt new file mode 100644 index 0000000000..58d8106471 --- /dev/null +++ b/libhwmon/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCES + wraphelper.h + wrapnvml.h wrapnvml.cpp + wrapadl.h wrapadl.cpp + wrapamdsysfs.h wrapamdsysfs.cpp +) + +add_library(hwmon ${SOURCES}) +target_link_libraries(hwmon devcore) +target_include_directories(hwmon PRIVATE ..) + +if (ETHASHCUDA) + find_package(CUDA REQUIRED) + target_include_directories(hwmon PUBLIC ${CUDA_INCLUDE_DIRS}) +endif() \ No newline at end of file diff --git a/libhwmon/wrapadl.cpp b/libhwmon/wrapadl.cpp new file mode 100644 index 0000000000..42e6744c4d --- /dev/null +++ b/libhwmon/wrapadl.cpp @@ -0,0 +1,193 @@ +/* +* Wrapper for ADL, inspired by wrapnvml from John E. Stone +* +* By Philipp Andreas - github@smurfy.de +*/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "wraphelper.h" +#include "wrapadl.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +void* ADL_API_CALL ADL_Main_Memory_Alloc(int iSize) +{ + void* lpBuffer = malloc(iSize); + return lpBuffer; +} + +wrap_adl_handle * wrap_adl_create() +{ + wrap_adl_handle *adlh = NULL; + +#if defined(_WIN64) + /* 64-bit Windows */ +#define libatiadlxx "atiadlxx.dll" +#elif defined(_WIN32) || defined(_MSC_VER) + /* 32-bit Windows */ +#define libatiadlxx "atiadlxx.dll" +#elif defined(__linux) && (defined(__i386__) || defined(__ARM_ARCH_7A__)) + /* 32-bit linux assumed */ +#define libatiadlxx "libatiadlxx.so" +#elif defined(__linux) + /* 64-bit linux assumed */ +#define libatiadlxx "libatiadlxx.so" +#else +#define libatiadlxx "" +#warning "Unrecognized platform: need ADL DLL path for this platform..." +return NULL; +#endif + +#if WIN32 + char tmp[512]; + ExpandEnvironmentStringsA(libatiadlxx, tmp, sizeof(tmp)); +#else + char tmp[512] = libatiadlxx; +#endif + + void *adl_dll = wrap_dlopen(tmp); + if (adl_dll == NULL) + return NULL; + + adlh = (wrap_adl_handle *)calloc(1, sizeof(wrap_adl_handle)); + + adlh->adl_dll = adl_dll; + + adlh->adlMainControlCreate = (wrap_adlReturn_t(*)(ADL_MAIN_MALLOC_CALLBACK, int)) + wrap_dlsym(adlh->adl_dll, "ADL_Main_Control_Create"); + adlh->adlAdapterNumberOfAdapters = (wrap_adlReturn_t(*)(int *)) + wrap_dlsym(adlh->adl_dll, "ADL_Adapter_NumberOfAdapters_Get"); + adlh->adlAdapterAdapterInfoGet = (wrap_adlReturn_t(*)(LPAdapterInfo, int)) + wrap_dlsym(adlh->adl_dll, "ADL_Adapter_AdapterInfo_Get"); + adlh->adlAdapterAdapterIdGet = (wrap_adlReturn_t(*)(int, int*)) + wrap_dlsym(adlh->adl_dll, "ADL_Adapter_ID_Get"); + adlh->adlOverdrive5TemperatureGet = (wrap_adlReturn_t(*)(int, int, ADLTemperature*)) + wrap_dlsym(adlh->adl_dll, "ADL_Overdrive5_Temperature_Get"); + adlh->adlOverdrive5FanSpeedGet = (wrap_adlReturn_t(*)(int, int, ADLFanSpeedValue*)) + wrap_dlsym(adlh->adl_dll, "ADL_Overdrive5_FanSpeed_Get"); + adlh->adlMainControlRefresh = (wrap_adlReturn_t(*)(void)) + wrap_dlsym(adlh->adl_dll, "ADL_Main_Control_Refresh"); + adlh->adlMainControlDestory = (wrap_adlReturn_t(*)(void)) + wrap_dlsym(adlh->adl_dll, "ADL_Main_Control_Destroy"); + + if (adlh->adlMainControlCreate == NULL || + adlh->adlMainControlDestory == NULL || + adlh->adlMainControlRefresh == NULL || + adlh->adlAdapterNumberOfAdapters == NULL || + adlh->adlAdapterAdapterInfoGet == NULL || + adlh->adlAdapterAdapterIdGet == NULL || + adlh->adlOverdrive5TemperatureGet == NULL || + adlh->adlOverdrive5FanSpeedGet == NULL + ) { +#if 0 + printf("Failed to obtain all required ADL function pointers\n"); +#endif + wrap_dlclose(adlh->adl_dll); + free(adlh); + return NULL; + } + + adlh->adlMainControlCreate(ADL_Main_Memory_Alloc, 1); + adlh->adlMainControlRefresh(); + + int logicalGpuCount = 0; + adlh->adlAdapterNumberOfAdapters(&logicalGpuCount); + + adlh->phys_logi_device_id = (int*)calloc(logicalGpuCount, sizeof(int)); + + adlh->adl_gpucount = 0; + int last_adapter = 0; + if (logicalGpuCount > 0) { + adlh->devs = (LPAdapterInfo)malloc(sizeof(AdapterInfo) * logicalGpuCount); + memset(adlh->devs, '\0', sizeof(AdapterInfo) * logicalGpuCount); + + adlh->devs->iSize = sizeof(adlh->devs); + + int res = adlh->adlAdapterAdapterInfoGet(adlh->devs, sizeof(AdapterInfo) * logicalGpuCount); + + for (int i = 0; i < logicalGpuCount; i++) { + int adapterIndex = adlh->devs[i].iAdapterIndex; + int adapterID = 0; + + res = adlh->adlAdapterAdapterIdGet(adapterIndex, &adapterID); + + if (res != WRAPADL_OK) { + continue; + } + + adlh->phys_logi_device_id[adlh->adl_gpucount] = adapterIndex; + + if (adapterID == last_adapter) { + continue; + } + last_adapter = adapterID; + adlh->adl_gpucount++; + } + } + + return adlh; +} + +int wrap_adl_destory(wrap_adl_handle *adlh) +{ + adlh->adlMainControlDestory(); + wrap_dlclose(adlh->adl_dll); + free(adlh); + return 0; +} + +int wrap_adl_get_gpucount(wrap_adl_handle *adlh, int *gpucount) +{ + *gpucount = adlh->adl_gpucount; + return 0; +} + +int wrap_adl_get_gpu_name(wrap_adl_handle *adlh, int gpuindex, char *namebuf, int bufsize) +{ + if (gpuindex < 0 || gpuindex >= adlh->adl_gpucount) + return -1; + + memcpy(namebuf, adlh->devs[adlh->phys_logi_device_id[gpuindex]].strAdapterName, bufsize); + return 0; +} + + +int wrap_adl_get_tempC(wrap_adl_handle *adlh, int gpuindex, unsigned int *tempC) +{ + wrap_adlReturn_t rc; + if (gpuindex < 0 || gpuindex >= adlh->adl_gpucount) + return -1; + + ADLTemperature *temperature = new ADLTemperature(); + rc = adlh->adlOverdrive5TemperatureGet(adlh->phys_logi_device_id[gpuindex], 0, temperature); + if (rc != WRAPADL_OK) { + return -1; + } + *tempC = unsigned(temperature->iTemperature / 1000); + free(temperature); + return 0; +} + +int wrap_adl_get_fanpcnt(wrap_adl_handle *adlh, int gpuindex, unsigned int *fanpcnt) +{ + wrap_adlReturn_t rc; + if (gpuindex < 0 || gpuindex >= adlh->adl_gpucount) + return -1; + + ADLFanSpeedValue *fan = new ADLFanSpeedValue(); + fan->iSpeedType = 1; + rc = adlh->adlOverdrive5FanSpeedGet(adlh->phys_logi_device_id[gpuindex], 0, fan); + if (rc != WRAPADL_OK) { + return -1; + } + *fanpcnt = unsigned(fan->iFanSpeed); + free(fan); + return 0; +} + +#if defined(__cplusplus) +} +#endif diff --git a/libhwmon/wrapadl.h b/libhwmon/wrapadl.h new file mode 100644 index 0000000000..c49f3ce4ea --- /dev/null +++ b/libhwmon/wrapadl.h @@ -0,0 +1,140 @@ +/* +* Wrapper for ADL, inspired by wrapnvml from John E. Stone +* +* By Philipp Andreas - github@smurfy.de +*/ + +#ifndef _WRAPADL_H_ +#define _WRAPADL_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef enum wrap_adlReturn_enum { + WRAPADL_OK= 0 +} wrap_adlReturn_t; + +// Some ADL defines and structs from adl sdk +#if defined (__MSC_VER) +#define ADL_API_CALL __cdecl +#elif defined (_WIN32) || defined (__WIN32__) +#define ADL_API_CALL __stdcall +#else +#define ADL_API_CALL +#endif + +typedef void* (ADL_API_CALL *ADL_MAIN_MALLOC_CALLBACK)(int); + +#define ADL_MAX_PATH 256 +typedef struct AdapterInfo +{ + /// \ALL_STRUCT_MEM + + /// Size of the structure. + int iSize; + /// The ADL index handle. One GPU may be associated with one or two index handles + int iAdapterIndex; + /// The unique device ID associated with this adapter. + char strUDID[ADL_MAX_PATH]; + /// The BUS number associated with this adapter. + int iBusNumber; + /// The driver number associated with this adapter. + int iDeviceNumber; + /// The function number. + int iFunctionNumber; + /// The vendor ID associated with this adapter. + int iVendorID; + /// Adapter name. + char strAdapterName[ADL_MAX_PATH]; + /// Display name. For example, "\\Display0" for Windows or ":0:0" for Linux. + char strDisplayName[ADL_MAX_PATH]; + /// Present or not; 1 if present and 0 if not present.It the logical adapter is present, the display name such as \\.\Display1 can be found from OS + int iPresent; + // @} + +#if defined (_WIN32) || defined (_WIN64) + /// \WIN_STRUCT_MEM + + /// Exist or not; 1 is exist and 0 is not present. + int iExist; + /// Driver registry path. + char strDriverPath[ADL_MAX_PATH]; + /// Driver registry path Ext for. + char strDriverPathExt[ADL_MAX_PATH]; + /// PNP string from Windows. + char strPNPString[ADL_MAX_PATH]; + /// It is generated from EnumDisplayDevices. + int iOSDisplayIndex; + // @} +#endif /* (_WIN32) || (_WIN64) */ + +#if defined (LINUX) + /// \LNX_STRUCT_MEM + + /// Internal X screen number from GPUMapInfo (DEPRICATED use XScreenInfo) + int iXScreenNum; + /// Internal driver index from GPUMapInfo + int iDrvIndex; + /// \deprecated Internal x config file screen identifier name. Use XScreenInfo instead. + char strXScreenConfigName[ADL_MAX_PATH]; + + // @} +#endif /* (LINUX) */ +} AdapterInfo, *LPAdapterInfo; + +typedef struct ADLTemperature +{ + /// Must be set to the size of the structure + int iSize; + /// Temperature in millidegrees Celsius. + int iTemperature; +} ADLTemperature; + +typedef struct ADLFanSpeedValue +{ + /// Must be set to the size of the structure + int iSize; + /// Possible valies: \ref ADL_DL_FANCTRL_SPEED_TYPE_PERCENT or \ref ADL_DL_FANCTRL_SPEED_TYPE_RPM + int iSpeedType; + /// Fan speed value + int iFanSpeed; + /// The only flag for now is: \ref ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED + int iFlags; +} ADLFanSpeedValue; + +/* +* Handle to hold the function pointers for the entry points we need, +* and the shared library itself. +*/ +typedef struct { + void *adl_dll; + int adl_gpucount; + int *phys_logi_device_id; + LPAdapterInfo devs; + wrap_adlReturn_t(*adlMainControlCreate)(ADL_MAIN_MALLOC_CALLBACK, int); + wrap_adlReturn_t(*adlAdapterNumberOfAdapters)(int *); + wrap_adlReturn_t(*adlAdapterAdapterInfoGet)(LPAdapterInfo, int); + wrap_adlReturn_t(*adlAdapterAdapterIdGet)(int, int*); + wrap_adlReturn_t(*adlOverdrive5TemperatureGet)(int, int, ADLTemperature*); + wrap_adlReturn_t(*adlOverdrive5FanSpeedGet)(int, int, ADLFanSpeedValue*); + wrap_adlReturn_t(*adlMainControlRefresh)(void); + wrap_adlReturn_t(*adlMainControlDestory)(void); +} wrap_adl_handle; + +wrap_adl_handle * wrap_adl_create(); +int wrap_adl_destory(wrap_adl_handle *adlh); + +int wrap_adl_get_gpucount(wrap_adl_handle *adlh, int *gpucount); + +int wrap_adl_get_gpu_name(wrap_adl_handle *adlh, int gpuindex, char *namebuf, int bufsize); + +int wrap_adl_get_tempC(wrap_adl_handle *adlh, int gpuindex, unsigned int *tempC); + +int wrap_adl_get_fanpcnt(wrap_adl_handle *adlh, int gpuindex, unsigned int *fanpcnt); + +#if defined(__cplusplus) +} +#endif + +#endif \ No newline at end of file diff --git a/libhwmon/wrapamdsysfs.cpp b/libhwmon/wrapamdsysfs.cpp new file mode 100644 index 0000000000..707c29b331 --- /dev/null +++ b/libhwmon/wrapamdsysfs.cpp @@ -0,0 +1,209 @@ +/* +* Wrapper for AMD SysFS on linux, using adapted code from amdcovc by matszpk +* +* By Philipp Andreas - github@smurfy.de +*/ +#include <iostream> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string> +#include <cstring> +#include <climits> +#include <limits> +#include <cstdint> +#include <algorithm> +#include <fstream> +#include <sys/types.h> +#if defined(__linux) +#include <dirent.h> +#endif +#include "wrapamdsysfs.h" + +static bool getFileContentValue(const char* filename, unsigned int& value) +{ + value = 0; + std::ifstream ifs(filename, std::ios::binary); + std::string line; + std::getline(ifs, line); + char* p = (char*)line.c_str(); + char* p2; + errno = 0; + value = strtoul(p, &p2, 0); + if (errno != 0) + return false; + return (p != p2); +} + + +wrap_amdsysfs_handle * wrap_amdsysfs_create() +{ + wrap_amdsysfs_handle *sysfsh = NULL; + +#if defined(__linux) + sysfsh = (wrap_amdsysfs_handle *)calloc(1, sizeof(wrap_amdsysfs_handle)); + + DIR* dirp = opendir("/sys/class/drm"); + if (dirp == nullptr) + return NULL; + + unsigned int gpucount = 0; + struct dirent* dire; + while ((dire = readdir(dirp)) != nullptr) + { + if (::strncmp(dire->d_name, "card", 4) != 0) + continue; // is not card directory + const char* p; + for (p = dire->d_name + 4; ::isdigit(*p); p++); + if (*p != 0) + continue; // is not card directory + unsigned int v = ::strtoul(dire->d_name + 4, nullptr, 10); + gpucount = std::max(gpucount, v + 1); + } + if (errno != 0) + { + closedir(dirp); + return NULL; + } + closedir(dirp); + + sysfsh->card_sysfs_device_id = (int*)calloc(gpucount, sizeof(int)); + sysfsh->sysfs_hwmon_id = (int*)calloc(gpucount, sizeof(int)); + + // filter AMD GPU cards and create mappings + char dbuf[120]; + int cardIndex = 0; + for (unsigned int i = 0; i < gpucount; i++) + { + sysfsh->card_sysfs_device_id[cardIndex] = -1; + sysfsh->sysfs_hwmon_id[cardIndex] = -1; + + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/vendor", i); + unsigned int vendorId = 0; + if (!getFileContentValue(dbuf, vendorId)) + continue; + if (vendorId != 4098) // if not AMD + continue; + + sysfsh->card_sysfs_device_id[cardIndex] = i; + cardIndex++; + } + + // Number of AMD cards found we do not care about non AMD cards + sysfsh->sysfs_gpucount = cardIndex; + + // Get hwmon directory index + for (int i = 0; i < sysfsh->sysfs_gpucount; i++) + { + int sysfsIdx = sysfsh->card_sysfs_device_id[i]; + + // Should not happen + if (sysfsIdx < 0) { + free(sysfsh); + return NULL; + } + + // search hwmon + errno = 0; + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/hwmon", sysfsIdx); + DIR* dirp = opendir(dbuf); + if (dirp == nullptr) { + free(sysfsh); + return NULL; + } + errno = 0; + struct dirent* dire; + unsigned int hwmonIndex = UINT_MAX; + while ((dire = readdir(dirp)) != nullptr) + { + if (::strncmp(dire->d_name, "hwmon", 5) != 0) + continue; // is not hwmon directory + const char* p; + for (p = dire->d_name + 5; ::isdigit(*p); p++); + if (*p != 0) + continue; // is not hwmon directory + errno = 0; + unsigned int v = ::strtoul(dire->d_name + 5, nullptr, 10); + hwmonIndex = std::min(hwmonIndex, v); + } + if (errno != 0) + { + closedir(dirp); + free(sysfsh); + return NULL; + } + closedir(dirp); + if (hwmonIndex == UINT_MAX) { + free(sysfsh); + return NULL; + } + + sysfsh->sysfs_hwmon_id[i] = hwmonIndex; + } +#endif + + return sysfsh; +} +int wrap_amdsysfs_destory(wrap_amdsysfs_handle *sysfsh) +{ + free(sysfsh); + return 0; +} + +int wrap_amdsysfs_get_gpucount(wrap_amdsysfs_handle *sysfsh, int *gpucount) +{ + *gpucount = sysfsh->sysfs_gpucount; + return 0; +} + +int wrap_amdsysfs_get_tempC(wrap_amdsysfs_handle *sysfsh, int index, unsigned int *tempC) +{ + int gpuindex = sysfsh->card_sysfs_device_id[index]; + if (gpuindex < 0 || index >= sysfsh->sysfs_gpucount) + return -1; + + int hwmonindex = sysfsh->sysfs_hwmon_id[index]; + if (hwmonindex < 0) + return -1; + + char dbuf[120]; + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/hwmon/hwmon%u/temp1_input", + gpuindex, hwmonindex); + + unsigned int temp = 0; + getFileContentValue(dbuf, temp); + + if (temp > 0) + *tempC = temp / 1000; + + return 0; +} + +int wrap_amdsysfs_get_fanpcnt(wrap_amdsysfs_handle *sysfsh, int index, unsigned int *fanpcnt) +{ + int gpuindex = sysfsh->card_sysfs_device_id[index]; + if (gpuindex < 0 || index >= sysfsh->sysfs_gpucount) + return -1; + + int hwmonindex = sysfsh->sysfs_hwmon_id[index]; + if (hwmonindex < 0) + return -1; + + unsigned int pwm = 0, pwmMax = 255, pwmMin = 0; + + char dbuf[120]; + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/hwmon/hwmon%u/pwm1", + gpuindex, hwmonindex); + getFileContentValue(dbuf, pwm); + + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/hwmon/hwmon%u/pwm1_max", + gpuindex, hwmonindex); + getFileContentValue(dbuf, pwmMax); + + snprintf(dbuf, 120, "/sys/class/drm/card%u/device/hwmon/hwmon%u/pwm1_min", + gpuindex, hwmonindex); + getFileContentValue(dbuf, pwmMin); + + *fanpcnt = double(pwm - pwmMin) / double(pwmMax - pwmMin) * 100.0; + return 0; +} \ No newline at end of file diff --git a/libhwmon/wrapamdsysfs.h b/libhwmon/wrapamdsysfs.h new file mode 100644 index 0000000000..05c637d031 --- /dev/null +++ b/libhwmon/wrapamdsysfs.h @@ -0,0 +1,25 @@ +/* +* Wrapper for AMD SysFS on linux, using adapted code from amdcovc by matszpk +* +* By Philipp Andreas - github@smurfy.de +*/ + +#ifndef _WRAPAMDSYSFS_H_ +#define _WRAPAMDSYSFS_H_ + +typedef struct { + int sysfs_gpucount; + int *card_sysfs_device_id; /* map cardidx to filesystem card idx */ + int *sysfs_hwmon_id; /* filesystem card idx to filesystem hwmon idx */ +} wrap_amdsysfs_handle; + +wrap_amdsysfs_handle * wrap_amdsysfs_create(); +int wrap_amdsysfs_destory(wrap_amdsysfs_handle *sysfsh); + +int wrap_amdsysfs_get_gpucount(wrap_amdsysfs_handle *sysfsh, int *gpucount); + +int wrap_amdsysfs_get_tempC(wrap_amdsysfs_handle *sysfsh, int index, unsigned int *tempC); + +int wrap_amdsysfs_get_fanpcnt(wrap_amdsysfs_handle *sysfsh, int index, unsigned int *fanpcnt); + +#endif \ No newline at end of file diff --git a/libhwmon/wraphelper.h b/libhwmon/wraphelper.h new file mode 100644 index 0000000000..5c627f7e6b --- /dev/null +++ b/libhwmon/wraphelper.h @@ -0,0 +1,28 @@ +/* +* Wrappers to emulate dlopen() on other systems like Windows +*/ +#if defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) +#include <windows.h> +static void *wrap_dlopen(const char *filename) { + return (void *)LoadLibrary(filename); +} +static void *wrap_dlsym(void *h, const char *sym) { + return (void *)GetProcAddress((HINSTANCE)h, sym); +} +static int wrap_dlclose(void *h) { + /* FreeLibrary returns nonzero on success */ + return (!FreeLibrary((HINSTANCE)h)); +} +#else +/* assume we can use dlopen itself... */ +#include <dlfcn.h> +static void *wrap_dlopen(const char *filename) { + return dlopen(filename, RTLD_NOW); +} +static void *wrap_dlsym(void *h, const char *sym) { + return dlsym(h, sym); +} +static int wrap_dlclose(void *h) { + return dlclose(h); +} +#endif \ No newline at end of file diff --git a/libhwmon/wrapnvml.cpp b/libhwmon/wrapnvml.cpp new file mode 100644 index 0000000000..757e8739de --- /dev/null +++ b/libhwmon/wrapnvml.cpp @@ -0,0 +1,261 @@ +/* + * A trivial little dlopen()-based wrapper library for the + * NVIDIA NVML library, to allow runtime discovery of NVML on an + * arbitrary system. This is all very hackish and simple-minded, but + * it serves my immediate needs in the short term until NVIDIA provides + * a static NVML wrapper library themselves, hopefully in + * CUDA 6.5 or maybe sometime shortly after. + * + * This trivial code is made available under the "new" 3-clause BSD license, + * and/or any of the GPL licenses you prefer. + * Feel free to use the code and modify as you see fit. + * + * John E. Stone - john.stone@gmail.com + * + * Modified to work with ethminer by + * + * Philipp Andreas - github@smurfy.de + */ + +#include <stdio.h> +#include <stdlib.h> +#include "wraphelper.h" +#include "wrapnvml.h" +#if ETH_ETHASHCUDA +#include "cuda_runtime.h" +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +wrap_nvml_handle * wrap_nvml_create() { + int i=0; + wrap_nvml_handle *nvmlh = NULL; + + /* + * We use hard-coded library installation locations for the time being... + * No idea where or if libnvidia-ml.so is installed on MacOS X, a + * deep scouring of the filesystem on one of the Mac CUDA build boxes + * I used turned up nothing, so for now it's not going to work on OSX. + */ +#if defined(_WIN64) + /* 64-bit Windows */ +#define libnvidia_ml "%PROGRAMFILES%/NVIDIA Corporation/NVSMI/nvml.dll" +#elif defined(_WIN32) || defined(_MSC_VER) + /* 32-bit Windows */ +#define libnvidia_ml "%PROGRAMFILES%/NVIDIA Corporation/NVSMI/nvml.dll" +#elif defined(__linux) && (defined(__i386__) || defined(__ARM_ARCH_7A__)) + /* 32-bit linux assumed */ +#define libnvidia_ml "libnvidia-ml.so" +#elif defined(__linux) + /* 64-bit linux assumed */ +#define libnvidia_ml "libnvidia-ml.so" +#else +#define libnvidia_ml "" +#warning "Unrecognized platform: need NVML DLL path for this platform..." +return NULL; +#endif + +#if WIN32 + char tmp[512]; + ExpandEnvironmentStringsA(libnvidia_ml, tmp, sizeof(tmp)); +#else + char tmp[512] = libnvidia_ml; +#endif + + void *nvml_dll = wrap_dlopen(tmp); + if (nvml_dll == NULL) + return NULL; + + nvmlh = (wrap_nvml_handle *) calloc(1, sizeof(wrap_nvml_handle)); + + nvmlh->nvml_dll = nvml_dll; + + nvmlh->nvmlInit = (wrap_nvmlReturn_t (*)(void)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlInit"); + nvmlh->nvmlDeviceGetCount = (wrap_nvmlReturn_t (*)(int *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetCount_v2"); + nvmlh->nvmlDeviceGetHandleByIndex = (wrap_nvmlReturn_t (*)(int, wrap_nvmlDevice_t *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetHandleByIndex_v2"); + nvmlh->nvmlDeviceGetPciInfo = (wrap_nvmlReturn_t (*)(wrap_nvmlDevice_t, wrap_nvmlPciInfo_t *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetPciInfo"); + nvmlh->nvmlDeviceGetName = (wrap_nvmlReturn_t (*)(wrap_nvmlDevice_t, char *, int)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetName"); + nvmlh->nvmlDeviceGetTemperature = (wrap_nvmlReturn_t (*)(wrap_nvmlDevice_t, int, unsigned int *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetTemperature"); + nvmlh->nvmlDeviceGetFanSpeed = (wrap_nvmlReturn_t (*)(wrap_nvmlDevice_t, unsigned int *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetFanSpeed"); + nvmlh->nvmlDeviceGetPowerUsage = (wrap_nvmlReturn_t (*)(wrap_nvmlDevice_t, unsigned int *)) + wrap_dlsym(nvmlh->nvml_dll, "nvmlDeviceGetPowerUsage"); + nvmlh->nvmlShutdown = (wrap_nvmlReturn_t (*)()) + wrap_dlsym(nvmlh->nvml_dll, "nvmlShutdown"); + + if (nvmlh->nvmlInit == NULL || + nvmlh->nvmlShutdown == NULL || + nvmlh->nvmlDeviceGetCount == NULL || + nvmlh->nvmlDeviceGetHandleByIndex == NULL || + nvmlh->nvmlDeviceGetPciInfo == NULL || + nvmlh->nvmlDeviceGetName == NULL || + nvmlh->nvmlDeviceGetTemperature == NULL || + nvmlh->nvmlDeviceGetFanSpeed == NULL || + nvmlh->nvmlDeviceGetPowerUsage == NULL + ) { +#if 0 + printf("Failed to obtain all required NVML function pointers\n"); +#endif + wrap_dlclose(nvmlh->nvml_dll); + free(nvmlh); + return NULL; + } + + nvmlh->nvmlInit(); + nvmlh->nvmlDeviceGetCount(&nvmlh->nvml_gpucount); + +#if ETH_ETHASHCUDA + /* Query CUDA device count, in case it doesn't agree with NVML, since */ + /* CUDA will only report GPUs with compute capability greater than 1.0 */ + if (cudaGetDeviceCount(&nvmlh->cuda_gpucount) != cudaSuccess) { +#if 0 + printf("Failed to query CUDA device count!\n"); +#endif + wrap_dlclose(nvmlh->nvml_dll); + free(nvmlh); + return NULL; + } +#endif + nvmlh->devs = (wrap_nvmlDevice_t *) calloc(nvmlh->nvml_gpucount, sizeof(wrap_nvmlDevice_t)); + +#if ETH_ETHASHCUDA + nvmlh->nvml_pci_domain_id = (unsigned int*) calloc(nvmlh->nvml_gpucount, sizeof(unsigned int)); + nvmlh->nvml_pci_bus_id = (unsigned int*) calloc(nvmlh->nvml_gpucount, sizeof(unsigned int)); + nvmlh->nvml_pci_device_id = (unsigned int*) calloc(nvmlh->nvml_gpucount, sizeof(unsigned int)); + nvmlh->nvml_cuda_device_id = (int*) calloc(nvmlh->nvml_gpucount, sizeof(int)); + nvmlh->cuda_nvml_device_id = (int*) calloc(nvmlh->cuda_gpucount, sizeof(int)); + + /* Obtain GPU device handles we're going to need repeatedly... */ + for (i=0; i<nvmlh->nvml_gpucount; i++) { + nvmlh->nvmlDeviceGetHandleByIndex(i, &nvmlh->devs[i]); + } + + /* Query PCI info for each NVML device, and build table for mapping of */ + /* CUDA device IDs to NVML device IDs and vice versa */ + for (i=0; i<nvmlh->nvml_gpucount; i++) { + wrap_nvmlPciInfo_t pciinfo; + nvmlh->nvmlDeviceGetPciInfo(nvmlh->devs[i], &pciinfo); + nvmlh->nvml_pci_domain_id[i] = pciinfo.domain; + nvmlh->nvml_pci_bus_id[i] = pciinfo.bus; + nvmlh->nvml_pci_device_id[i] = pciinfo.device; + } + + /* build mapping of NVML device IDs to CUDA IDs */ + for (i=0; i<nvmlh->nvml_gpucount; i++) { + nvmlh->nvml_cuda_device_id[i] = -1; + } + for (i=0; i<nvmlh->cuda_gpucount; i++) { + cudaDeviceProp props; + nvmlh->cuda_nvml_device_id[i] = -1; + + if (cudaGetDeviceProperties(&props, i) == cudaSuccess) { + int j; + for (j=0; j<nvmlh->nvml_gpucount; j++) { + if ((nvmlh->nvml_pci_domain_id[j] == props.pciDomainID) && + (nvmlh->nvml_pci_bus_id[j] == props.pciBusID) && + (nvmlh->nvml_pci_device_id[j] == props.pciDeviceID)) { +#if 0 + printf("CUDA GPU[%d] matches NVML GPU[%d]\n", i, j); +#endif + nvmlh->nvml_cuda_device_id[j] = i; + nvmlh->cuda_nvml_device_id[i] = j; + } + } + } + } + +#endif + + return nvmlh; +} + + +int wrap_nvml_destroy(wrap_nvml_handle *nvmlh) { + nvmlh->nvmlShutdown(); + + wrap_dlclose(nvmlh->nvml_dll); + free(nvmlh); + return 0; +} + + +int wrap_nvml_get_gpucount(wrap_nvml_handle *nvmlh, int *gpucount) { + *gpucount = nvmlh->nvml_gpucount; + return 0; +} + +int wrap_cuda_get_gpucount(wrap_nvml_handle *nvmlh, int *gpucount) { + *gpucount = nvmlh->cuda_gpucount; + return 0; +} + +int wrap_nvml_get_gpu_name(wrap_nvml_handle *nvmlh, + int gpuindex, + char *namebuf, + int bufsize) { + if (gpuindex < 0 || gpuindex >= nvmlh->nvml_gpucount) + return -1; + + if (nvmlh->nvmlDeviceGetName(nvmlh->devs[gpuindex], namebuf, bufsize) != WRAPNVML_SUCCESS) + return -1; + + return 0; +} + + +int wrap_nvml_get_tempC(wrap_nvml_handle *nvmlh, + int gpuindex, unsigned int *tempC) { + wrap_nvmlReturn_t rc; + if (gpuindex < 0 || gpuindex >= nvmlh->nvml_gpucount) + return -1; + + rc = nvmlh->nvmlDeviceGetTemperature(nvmlh->devs[gpuindex], 0u /* NVML_TEMPERATURE_GPU */, tempC); + if (rc != WRAPNVML_SUCCESS) { + return -1; + } + + return 0; +} + + +int wrap_nvml_get_fanpcnt(wrap_nvml_handle *nvmlh, + int gpuindex, unsigned int *fanpcnt) { + wrap_nvmlReturn_t rc; + if (gpuindex < 0 || gpuindex >= nvmlh->nvml_gpucount) + return -1; + + rc = nvmlh->nvmlDeviceGetFanSpeed(nvmlh->devs[gpuindex], fanpcnt); + if (rc != WRAPNVML_SUCCESS) { + return -1; + } + + return 0; +} + + +int wrap_nvml_get_power_usage(wrap_nvml_handle *nvmlh, + int gpuindex, + unsigned int *milliwatts) { + if (gpuindex < 0 || gpuindex >= nvmlh->nvml_gpucount) + return -1; + + if (nvmlh->nvmlDeviceGetPowerUsage(nvmlh->devs[gpuindex], milliwatts) != WRAPNVML_SUCCESS) + return -1; + + return 0; +} + + +#if defined(__cplusplus) +} +#endif + + diff --git a/libhwmon/wrapnvml.h b/libhwmon/wrapnvml.h new file mode 100644 index 0000000000..cf818d70ea --- /dev/null +++ b/libhwmon/wrapnvml.h @@ -0,0 +1,127 @@ +/* + * A trivial little dlopen()-based wrapper library for the + * NVIDIA NVML library, to allow runtime discovery of NVML on an + * arbitrary system. This is all very hackish and simple-minded, but + * it serves my immediate needs in the short term until NVIDIA provides + * a static NVML wrapper library themselves, hopefully in + * CUDA 6.5 or maybe sometime shortly after. + * + * This trivial code is made available under the "new" 3-clause BSD license, + * and/or any of the GPL licenses you prefer. + * Feel free to use the code and modify as you see fit. + * + * John E. Stone - john.stone@gmail.com + * + */ + +#ifndef _WRAPNVML_H_ +#define _WRAPNVML_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * Ugly hacks to avoid dependencies on the real nvml.h until it starts + * getting included with the CUDA toolkit or a GDK that's got a known + * install location, etc. + */ +typedef enum wrap_nvmlReturn_enum { + WRAPNVML_SUCCESS = 0 +} wrap_nvmlReturn_t; + +typedef void * wrap_nvmlDevice_t; + +/* our own version of the PCI info struct */ +typedef struct { + char bus_id_str[16]; /* string form of bus info */ + unsigned int domain; + unsigned int bus; + unsigned int device; + unsigned int pci_device_id; /* combined device and vendor id */ + unsigned int pci_subsystem_id; + unsigned int res0; /* NVML internal use only */ + unsigned int res1; + unsigned int res2; + unsigned int res3; +} wrap_nvmlPciInfo_t; + + +/* + * Handle to hold the function pointers for the entry points we need, + * and the shared library itself. + */ +typedef struct { + void *nvml_dll; + int nvml_gpucount; + int cuda_gpucount; + unsigned int *nvml_pci_domain_id; + unsigned int *nvml_pci_bus_id; + unsigned int *nvml_pci_device_id; + int *nvml_cuda_device_id; /* map NVML dev to CUDA dev */ + int *cuda_nvml_device_id; /* map CUDA dev to NVML dev */ + wrap_nvmlDevice_t *devs; + wrap_nvmlReturn_t (*nvmlInit)(void); + wrap_nvmlReturn_t (*nvmlDeviceGetCount)(int *); + wrap_nvmlReturn_t (*nvmlDeviceGetHandleByIndex)(int, wrap_nvmlDevice_t *); + wrap_nvmlReturn_t (*nvmlDeviceGetPciInfo)(wrap_nvmlDevice_t, wrap_nvmlPciInfo_t *); + wrap_nvmlReturn_t (*nvmlDeviceGetName)(wrap_nvmlDevice_t, char *, int); + wrap_nvmlReturn_t (*nvmlDeviceGetTemperature)(wrap_nvmlDevice_t, int, unsigned int *); + wrap_nvmlReturn_t (*nvmlDeviceGetFanSpeed)(wrap_nvmlDevice_t, unsigned int *); + wrap_nvmlReturn_t (*nvmlDeviceGetPowerUsage)(wrap_nvmlDevice_t, unsigned int *); + wrap_nvmlReturn_t (*nvmlShutdown)(void); +} wrap_nvml_handle; + + +wrap_nvml_handle * wrap_nvml_create(); +int wrap_nvml_destroy(wrap_nvml_handle *nvmlh); + +/* + * Query the number of GPUs seen by NVML + */ +int wrap_nvml_get_gpucount(wrap_nvml_handle *nvmlh, int *gpucount); + +/* + * Query the number of GPUs seen by CUDA + */ +int wrap_cuda_get_gpucount(wrap_nvml_handle *nvmlh, int *gpucount); + + +/* + * query the name of the GPU model from the CUDA device ID + * + */ +int wrap_nvml_get_gpu_name(wrap_nvml_handle *nvmlh, + int gpuindex, + char *namebuf, + int bufsize); + +/* + * Query the current GPU temperature (Celsius), from the CUDA device ID + */ +int wrap_nvml_get_tempC(wrap_nvml_handle *nvmlh, + int gpuindex, unsigned int *tempC); + +/* + * Query the current GPU fan speed (percent) from the CUDA device ID + */ +int wrap_nvml_get_fanpcnt(wrap_nvml_handle *nvmlh, + int gpuindex, unsigned int *fanpcnt); + +/* + * Query the current GPU power usage in millwatts from the CUDA device ID + * + * This feature is only available on recent GPU generations and may be + * limited in some cases only to Tesla series GPUs. + * If the query is run on an unsupported GPU, this routine will return -1. + */ +int wrap_nvml_get_power_usage(wrap_nvml_handle *nvmlh, + int gpuindex, + unsigned int *milliwatts); + + +#if defined(__cplusplus) +} +#endif + +#endif