diff --git a/CMakeLists.txt b/CMakeLists.txt index d40d76ef2..2641f1bda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,14 @@ function(configureProject) add_definitions(-DETH_GUI) endif() + if (CPUID_FOUND) + add_definitions(-DETH_CPUID) + endif() + + if (CURL_FOUND) + add_definitions(-DETH_CURL) + endif() + add_definitions(-DETH_TRUE) endfunction() @@ -272,8 +280,6 @@ elseif (BUNDLE STREQUAL "user") set(TESTS OFF) endif () -configureProject() - # Default CMAKE_BUILD_TYPE to "Release". set(CMAKE_BUILD_TYPE CACHE STRING "Release") if ("x${CMAKE_BUILD_TYPE}" STREQUAL "x") @@ -290,6 +296,10 @@ if ("x${TARGET_PLATFORM}" STREQUAL "x") endif () endif () +include(EthDependencies) + +configureProject() + message("------------------------------------------------------------------------") message("-- CMake Version ${CMAKE_VERSION}") message("-- CMAKE_BUILD_TYPE Build type ${CMAKE_BUILD_TYPE}") @@ -297,6 +307,8 @@ message("-- TARGET_PLATFORM Target platform ${TARGET_P message("-- BUNDLE Build bundle ${BUNDLE}") message("--------------------------------------------------------------- features") message("-- Chromium support ${ETH_HAVE_WEBENGINE}") +message("-- Hardware identification support ${CPUID_FOUND}") +message("-- HTTP Request support ${CURL_FOUND}") message("-- VMTRACE VM execution tracing ${VMTRACE}") message("-- PROFILING Profiling support ${PROFILING}") message("-- FATDB Full database exploring ${FATDB}") @@ -322,9 +334,7 @@ endif () include(EthCompilerSettings) message("-- CXXFLAGS: ${CMAKE_CXX_FLAGS}") - # this must be an include, as a function it would mess up with variable scope! -include(EthDependencies) include(EthExecutableHelper) createBuildInfo() diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 61c87efd2..4c6b7841a 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -59,7 +59,6 @@ if (JSONRPC) find_package(MHD) message(" - microhttpd header: ${MHD_INCLUDE_DIRS}") message(" - microhttpd lib : ${MHD_LIBRARIES}") - endif() #JSONRPC # TODO readline package does not yet check for correct version number @@ -86,7 +85,7 @@ endif() # TODO it is also not required in msvc build find_package (Gmp 6.0.0) if (GMP_FOUND) - message(" - gmp Header: ${GMP_INCLUDE_DIRS}") + message(" - gmp header: ${GMP_INCLUDE_DIRS}") message(" - gmp lib : ${GMP_LIBRARIES}") endif() @@ -96,6 +95,13 @@ find_package (CURL) message(" - curl header: ${CURL_INCLUDE_DIRS}") message(" - curl lib : ${CURL_LIBRARIES}") +# cpuid required for eth +find_package (Cpuid) +if (CPUID_FOUND) + message(" - cpuid header: ${CPUID_INCLUDE_DIRS}") + message(" - cpuid lib : ${CPUID_LIBRARIES}") +endif() + # find location of jsonrpcstub find_program(ETH_JSON_RPC_STUB jsonrpcstub) message(" - jsonrpcstub location : ${ETH_JSON_RPC_STUB}") diff --git a/cmake/FindCpuid.cmake b/cmake/FindCpuid.cmake new file mode 100644 index 000000000..8b590f44b --- /dev/null +++ b/cmake/FindCpuid.cmake @@ -0,0 +1,33 @@ +# Find libcpuid +# +# Find the libcpuid includes and library +# +# if you nee to add a custom library search path, do it via via CMAKE_PREFIX_PATH +# +# This module defines +# CPUID_INCLUDE_DIRS, where to find header, etc. +# CPUID_LIBRARIES, the libraries needed to use cpuid. +# CPUID_FOUND, If false, do not try to use cpuid. + +# only look in default directories +find_path( + CPUID_INCLUDE_DIR + NAMES libcpuid/libcpuid.h + DOC "libcpuid include dir" + ) + +find_library( + CPUID_LIBRARY + NAMES cpuid + DOC "libcpuid library" + ) + +set(CPUID_INCLUDE_DIRS ${CPUID_INCLUDE_DIR}) +set(CPUID_LIBRARIES ${CPUID_LIBRARY}) + +# handle the QUIETLY and REQUIRED arguments and set CPUID_FOUND to TRUE +# if all listed variables are TRUE, hide their existence from configuration view +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(cpuid DEFAULT_MSG CPUID_INCLUDE_DIR CPUID_LIBRARY) +mark_as_advanced (CPUID_INCLUDE_DIR CPUID_LIBRARY) + diff --git a/eth/main.cpp b/eth/main.cpp index 5bc6f9911..7da725b6c 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -89,7 +89,7 @@ void interactiveHelp() << " send Execute a given transaction with current secret." << endl << " contract Create a new contract with current secret." << endl << " peers List the peers that are connected" << endl -#if ETH_FATDB +#if ETH_FATDB || !ETH_TRUE << " listaccounts List the accounts on the network." << endl << " listcontracts List the contracts on the network." << endl #endif @@ -112,24 +112,31 @@ void help() << " -b,--bootstrap Connect to the default Ethereum peerserver." << endl << " -B,--block-fees Set the block fee profit in the reference unit e.g. ¢ (Default: 15)." << endl << " -c,--client-name Add a name to your client's version string (default: blank)." << endl - << " -C,--check-pow Check PoW credentials for validity." << endl + << " -C,--cpu When mining, use the CPU." << endl << " -d,--db-path Load database from path (default: ~/.ethereum " << endl << " /Etherum or Library/Application Support/Ethereum)." << endl + << " --benchmark-warmup Set the duration of warmup for the benchmark tests (default: 15)." << endl + << " --benchmark-trial Set the duration for each trial for the benchmark tests (default: 3)." << endl + << " --benchmark-trials Set the duration of warmup for the benchmark tests (default: 5)." << endl << " -D,--create-dag Create the DAG in preparation for mining on given block and exit." << endl << " -e,--ether-price Set the ether price in the reference unit e.g. ¢ (Default: 30.679)." << endl << " -E,--export Export file as a concatenated series of blocks and exit." << endl << " --from Export only from block n; n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --to Export only to block n (inclusive); n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --only Equivalent to --export-from n --export-to n." << endl - << " -f,--force-mining Mine even when there are no transaction to mine (Default: off)" << endl + << " -f,--force-mining Mine even when there are no transactions to mine (Default: off)" << endl +#if ETH_JSONRPC || !ETH_TRUE + << " -F,--farm Put into mining farm mode (default GPU with CPU as fallback)." << endl +#endif + << " -G,--gpu When miningm use the GPU." << endl << " -h,--help Show this help message and exit." << endl << " -i,--interactive Enter interactive mode (default: non-interactive)." << endl << " -I,--import Import file as a concatenated series of blocks and exit." << endl -#if ETH_JSONRPC +#if ETH_JSONRPC || !ETH_TRUE << " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl << " --json-rpc-port Specify JSON-RPC server port (implies '-j', default: " << SensibleHttpPort << ")." << endl #endif -#if ETH_EVMJIT +#if ETH_EVMJIT || !ETH_TRUE << " -J,--jit Enable EVM JIT (default: off)." << endl #endif << " -K,--kill First kill the blockchain." << endl @@ -137,17 +144,20 @@ void help() << " -l,--listen Listen on the given IP for incoming connections (default: 0.0.0.0)." << endl << " -u,--public-ip Force public ip to given (default: auto)." << endl << " -m,--mining Enable mining, optionally for a specified number of blocks (Default: off)" << endl - << " -n,-u,--upnp Use upnp for NAT (default: on)." << endl + << " -M,--benchmark Benchmark for mining and exit; use with --cpu and --gpu." << endl << " -o,--mode Start a full node or a peer node (Default: full)." << endl << " -p,--port Connect to remote port (default: 30303)." << endl << " -P,--priority <0 - 100> Default % priority of a transaction (default: 50)." << endl + << " --phone-home When benchmarking, publish results (Default: on)" << endl << " -R,--rebuild First rebuild the blockchain from the existing database." << endl << " -r,--remote Connect to remote host (default: none)." << endl << " -s,--secret Set the secret key for use with send command (default: auto)." << endl << " -S,--temporary-secret Set the secret key for use with send command, for this session only." << endl + << " -u,--upnp Use upnp for NAT (default: on)." << endl << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (Default: 8)." << endl - << " -x,--peers Attempt to connect to given number of peers (Default: 5)." << endl << " -V,--version Show the version and exit." << endl + << " -w,--check-pow Check PoW credentials for validity." << endl + << " -x,--peers Attempt to connect to given number of peers (Default: 5)." << endl ; exit(0); } @@ -221,7 +231,9 @@ enum class OperationMode Node, Import, Export, - DAGInit + DAGInit, + Benchmark, + Farm }; enum class Format @@ -231,6 +243,70 @@ enum class Format Human }; +enum class MinerType +{ + CPU, + GPU +}; + +void doBenchmark(MinerType _m, bool _phoneHome, unsigned _warmupDuration = 15, unsigned _trialDuration = 3, unsigned _trials = 5) +{ + BlockInfo genesis = CanonBlockChain::genesis(); + genesis.difficulty = 1 << 18; + cdebug << genesis.boundary(); + + GenericFarm f; + f.onSolutionFound([&](ProofOfWork::Solution) { return false; }); + + string platformInfo = _m == MinerType::CPU ? ProofOfWork::CPUMiner::platformInfo() : _m == MinerType::GPU ? ProofOfWork::GPUMiner::platformInfo() : ""; + cout << "Benchmarking on platform: " << platformInfo << endl; + + cout << "Preparing DAG..." << endl; + Ethash::prep(genesis); + + genesis.difficulty = u256(1) << 63; + genesis.noteDirty(); + f.setWork(genesis); + if (_m == MinerType::CPU) + f.startCPU(); + else if (_m == MinerType::GPU) + f.startGPU(); + + map results; + uint64_t mean = 0; + uint64_t innerMean = 0; + for (unsigned i = 0; i <= _trials; ++i) + { + if (!i) + cout << "Warming up..." << endl; + else + cout << "Trial " << i << "... " << flush; + this_thread::sleep_for(chrono::seconds(i ? _trialDuration : _warmupDuration)); + + auto mp = f.miningProgress(); + f.resetMiningProgress(); + if (!i) + continue; + auto rate = mp.rate(); + + cout << rate << endl; + results[rate] = mp; + mean += rate; + if (i > 1 && i < 5) + innerMean += rate; + } + f.stop(); + cout << "min/mean/max: " << results.begin()->second.rate() << "/" << (mean / _trials) << "/" << results.rbegin()->second.rate() << " H/s" << endl; + cout << "inner mean: " << (innerMean / (_trials - 2)) << " H/s" << endl; + + (void)_phoneHome; + if (_phoneHome) + { + // TODO: send f.miningInfo() along with f.platformInfo() to Marian. + } + exit(0); +} + int main(int argc, char** argv) { // Init defaults @@ -240,6 +316,9 @@ int main(int argc, char** argv) OperationMode mode = OperationMode::Node; string dbPath; + /// Mining options + MinerType minerType = MinerType::CPU; + /// File name for import/export. string filename; @@ -287,6 +366,12 @@ int main(int argc, char** argv) double etherPrice = 30.679; double blockFees = 15.0; + /// Benchmarking params + bool phoneHome = true; + unsigned benchmarkWarmup = 15; + unsigned benchmarkTrial = 3; + unsigned benchmarkTrials = 5; + string configFile = getDataDir() + "/config.rlp"; bytes b = contents(configFile); if (b.size()) @@ -319,6 +404,21 @@ int main(int argc, char** argv) mode = OperationMode::Export; filename = argv[++i]; } + else if (arg == "-F" || arg == "--farm") + mode = OperationMode::Farm; + else if (arg == "--phone-home" && i + 1 < argc) + { + string m = argv[++i]; + if (isTrue(m)) + phoneHome = true; + else if (isFalse(m)) + phoneHome = false; + else + { + cerr << "Bad " << arg << " option: " << m << endl; + return -1; + } + } else if (arg == "--format" && i + 1 < argc) { string m = argv[++i]; @@ -355,6 +455,33 @@ int main(int argc, char** argv) return -1; } } + else if (arg == "--benchmark-warmup" && i + 1 < argc) + try { + benchmarkWarmup = stol(argv[++i]); + } + catch (...) + { + cerr << "Bad " << arg << " option: " << argv[i] << endl; + return -1; + } + else if (arg == "--benchmark-trial" && i + 1 < argc) + try { + benchmarkTrial = stol(argv[++i]); + } + catch (...) + { + cerr << "Bad " << arg << " option: " << argv[i] << endl; + return -1; + } + else if (arg == "--benchmark-trials" && i + 1 < argc) + try { + benchmarkTrials = stol(argv[++i]); + } + catch (...) + { + cerr << "Bad " << arg << " option: " << argv[i] << endl; + return -1; + } else if (arg == "-K" || arg == "--kill-blockchain" || arg == "--kill") killChain = WithExisting::Kill; else if (arg == "-B" || arg == "--rebuild") @@ -362,8 +489,7 @@ int main(int argc, char** argv) else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc) clientName = argv[++i]; else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc) - try - { + try { coinbase = h160(fromHex(argv[++i], WhenError::Throw)); } catch (BadHexCharacter&) @@ -376,6 +502,10 @@ int main(int argc, char** argv) cerr << "Bad " << arg << " option: " << argv[i] << endl; return -1; } + else if (arg == "-C" || arg == "--cpu") + minerType = MinerType::CPU; + else if (arg == "-G" || arg == "--gpu") + minerType = MinerType::GPU; else if ((arg == "-s" || arg == "--secret") && i + 1 < argc) sigKey = KeyPair(h256(fromHex(argv[++i]))); else if ((arg == "-S" || arg == "--session-secret") && i + 1 < argc) @@ -405,7 +535,7 @@ int main(int argc, char** argv) return -1; } } - else if ((arg == "-C" || arg == "--check-pow") && i + 4 < argc) + else if ((arg == "-w" || arg == "--check-pow") && i + 4 < argc) { string m; try @@ -442,6 +572,8 @@ int main(int argc, char** argv) return -1; } } + else if (arg == "-M" || arg == "--benchmark") + mode = OperationMode::Benchmark; else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc) { try @@ -564,6 +696,9 @@ int main(int argc, char** argv) if (mode == OperationMode::DAGInit && !(initDAG == LatestBlock || initDAG == PendingBlock)) doInitDAG(initDAG); + if (mode == OperationMode::Benchmark) + doBenchmark(minerType, phoneHome, benchmarkWarmup, benchmarkTrial, benchmarkTrials); + if (!clientName.empty()) clientName += "/"; diff --git a/libethash-cl/ethash_cl_miner.cpp b/libethash-cl/ethash_cl_miner.cpp index 016d8af58..c9a621e29 100644 --- a/libethash-cl/ethash_cl_miner.cpp +++ b/libethash-cl/ethash_cl_miner.cpp @@ -57,6 +57,33 @@ ethash_cl_miner::ethash_cl_miner() { } +std::string ethash_cl_miner::platform_info() +{ + std::vector platforms; + cl::Platform::get(&platforms); + if (platforms.empty()) + { + debugf("No OpenCL platforms found.\n"); + return std::string(); + } + + // get GPU device of the default platform + std::vector devices; + platforms[0].getDevices(CL_DEVICE_TYPE_ALL, &devices); + if (devices.empty()) + { + debugf("No OpenCL devices found.\n"); + return std::string(); + } + + // use default device + unsigned device_num = 0; + cl::Device& device = devices[device_num]; + std::string device_version = device.getInfo(); + + return "{ platform: '" + platforms[0].getInfo() + "', device: '" + device.getInfo() + "', version: '" + device_version + "' }"; +} + void ethash_cl_miner::finish() { if (m_queue()) diff --git a/libethash-cl/ethash_cl_miner.h b/libethash-cl/ethash_cl_miner.h index d3d9f0223..079000f55 100644 --- a/libethash-cl/ethash_cl_miner.h +++ b/libethash-cl/ethash_cl_miner.h @@ -23,6 +23,7 @@ public: ethash_cl_miner(); bool init(ethash_params const& params, std::function _fillDAG, unsigned workgroup_size = 64); + static std::string platform_info(); void finish(); void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); diff --git a/libethcore/CMakeLists.txt b/libethcore/CMakeLists.txt index f562bc948..020480d67 100644 --- a/libethcore/CMakeLists.txt +++ b/libethcore/CMakeLists.txt @@ -11,11 +11,15 @@ aux_source_directory(. SRC_LIST) include_directories(BEFORE ..) include_directories(${Boost_INCLUDE_DIRS}) +if (CPUID_FOUND) + include_directories(${Cpuid_INCLUDE_DIRS}) +endif () set(EXECUTABLE ethcore) file(GLOB HEADERS "*.h") + if(ETH_STATIC) add_library(${EXECUTABLE} STATIC ${SRC_LIST} ${HEADERS}) else() @@ -25,6 +29,9 @@ endif() target_link_libraries(${EXECUTABLE} ethash) target_link_libraries(${EXECUTABLE} devcrypto) target_link_libraries(${EXECUTABLE} devcore) +if (CPUID_FOUND) + target_link_libraries(${EXECUTABLE} ${CPUID_LIBRARIES}) +endif () install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libethcore/Ethash.cpp b/libethcore/Ethash.cpp index a777205e9..632bc278d 100644 --- a/libethcore/Ethash.cpp +++ b/libethcore/Ethash.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,10 @@ #if ETH_ETHASHCL || !ETH_TRUE #include #endif +#if ETH_CPUID || !ETH_TRUE +#define HAVE_STDINT_H +#include +#endif #include "BlockInfo.h" #include "EthashAux.h" using namespace std; @@ -142,6 +147,58 @@ void Ethash::CPUMiner::workLoop() } } +static string jsonEncode(map const& _m) +{ + string ret = "{"; + + for (auto const& i: _m) + { + string k = boost::replace_all_copy(boost::replace_all_copy(i.first, "\\", "\\\\"), "'", "\\'"); + string v = boost::replace_all_copy(boost::replace_all_copy(i.second, "\\", "\\\\"), "'", "\\'"); + if (ret.size() > 1) + ret += ", "; + ret += "\"" + k + "\":\"" + v + "\""; + } + + return ret + "}"; +} + +std::string Ethash::CPUMiner::platformInfo() +{ + string baseline = toString(std::thread::hardware_concurrency()) + "-thread CPU"; +#if ETH_CPUID || !ETH_TRUE + if (!cpuid_present()) + return baseline; + struct cpu_raw_data_t raw; + struct cpu_id_t data; + if (cpuid_get_raw_data(&raw) < 0) + return baseline; + if (cpu_identify(&raw, &data) < 0) + return baseline; + map m; + m["vendor"] = data.vendor_str; + m["codename"] = data.cpu_codename; + m["brand"] = data.brand_str; + m["L1 cache"] = toString(data.l1_data_cache); + m["L2 cache"] = toString(data.l2_cache); + m["L3 cache"] = toString(data.l3_cache); + m["cores"] = toString(data.num_cores); + m["threads"] = toString(data.num_logical_cpus); + m["clocknominal"] = toString(cpu_clock_by_os()); + m["clocktested"] = toString(cpu_clock_measure(200, 0)); + /* + printf(" MMX : %s\n", data.flags[CPU_FEATURE_MMX] ? "present" : "absent"); + printf(" MMX-extended: %s\n", data.flags[CPU_FEATURE_MMXEXT] ? "present" : "absent"); + printf(" SSE : %s\n", data.flags[CPU_FEATURE_SSE] ? "present" : "absent"); + printf(" SSE2 : %s\n", data.flags[CPU_FEATURE_SSE2] ? "present" : "absent"); + printf(" 3DNow! : %s\n", data.flags[CPU_FEATURE_3DNOW] ? "present" : "absent"); + */ + return jsonEncode(m); +#else + return baseline; +#endif +} + #if ETH_ETHASHCL || !ETH_TRUE class EthashCLHook: public ethash_cl_miner::search_hook @@ -258,6 +315,11 @@ void Ethash::GPUMiner::pause() stopWorking(); } +std::string Ethash::GPUMiner::platformInfo() +{ + return ethash_cl_miner::platform_info(); +} + #endif } diff --git a/libethcore/Ethash.h b/libethcore/Ethash.h index 06e6b9e96..0d33d43a8 100644 --- a/libethcore/Ethash.h +++ b/libethcore/Ethash.h @@ -85,6 +85,7 @@ public: CPUMiner(ConstructionInfo const& _ci): Miner(_ci), Worker("miner" + toString(index())) {} static unsigned instances() { return std::thread::hardware_concurrency(); } + static std::string platformInfo(); protected: void kickOff() override @@ -109,6 +110,7 @@ public: ~GPUMiner(); static unsigned instances() { return 1; } + static std::string platformInfo(); protected: void kickOff() override; diff --git a/libethcore/Miner.h b/libethcore/Miner.h index 51a0ff6f6..71e952d5c 100644 --- a/libethcore/Miner.h +++ b/libethcore/Miner.h @@ -42,13 +42,14 @@ struct MiningProgress // MiningProgress& operator+=(MiningProgress const& _mp) { hashes += _mp.hashes; ms = std::max(ms, _mp.ms); return *this; } uint64_t hashes = 0; ///< Total number of hashes computed. uint64_t ms = 0; ///< Total number of milliseconds of mining thus far. + uint64_t rate() const { return hashes * 1000 / ms; } }; struct MineInfo: public MiningProgress {}; inline std::ostream& operator<<(std::ostream& _out, MiningProgress _p) { - _out << (_p.hashes * 1000 / _p.ms) << "H/s = " << _p.hashes << " hashes / " << (double(_p.ms) / 1000) << "s"; + _out << _p.rate() << " H/s = " << _p.hashes << " hashes / " << (double(_p.ms) / 1000) << " s"; return _out; } @@ -112,7 +113,9 @@ public: m_hashCount = 0; } - uint64_t hashCount() { return m_hashCount; } + uint64_t hashCount() const { return m_hashCount; } + + void resetHashCount() { m_hashCount = 0; } unsigned index() const { return m_index; } diff --git a/libethereum/Farm.h b/libethereum/Farm.h index 26d4b139e..869925098 100644 --- a/libethereum/Farm.h +++ b/libethereum/Farm.h @@ -118,6 +118,17 @@ public: return m_progress; } + /** + * @brief Reset the mining progess counter. + */ + void resetMiningProgress() + { + ETH_READ_GUARDED(x_minerWork) + for (auto const& i: m_miners) + i->resetHashCount(); + resetTimer(); + } + using SolutionFound = std::function; /**