diff options
Diffstat (limited to 'tests/functional_tests/mining.py')
-rwxr-xr-x | tests/functional_tests/mining.py | 116 |
1 files changed, 106 insertions, 10 deletions
diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index 50bfd32a2..7ecbdeed5 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -31,6 +31,10 @@ from __future__ import print_function import time import os +import math +import monotonic +import util_resources +import multiprocessing """Test daemon mining RPC calls @@ -38,6 +42,10 @@ Test the following RPCs: - start_mining - stop_mining - mining_status + +Control the behavior with these environment variables: + MINING_NO_MEASUREMENT - set to anything to use large enough and fixed mining timeouts + MINING_SILENT - set to anything to disable mining logging """ from framework.daemon import Daemon @@ -71,12 +79,23 @@ class MiningTest(): def mine(self, via_daemon): print("Test mining via " + ("daemon" if via_daemon else "wallet")) + cores_init = multiprocessing.cpu_count() # RX init uses all cores + cores_mine = 1 # Mining uses a parametric number of cores + is_mining_measurent = 'MINING_NO_MEASUREMENT' not in os.environ + + if is_mining_measurent: # A dynamic calculation of the CPU power requested + time_pi_single_cpu = self.measure_cpu_power_get_time(cores_mine) + time_pi_all_cores = self.measure_cpu_power_get_time(cores_init) + # This is the last measurement, since it takes very little time and can be placed timewise-closer to the mining itself. + available_ram = self.get_available_ram() # So far no ideas how to use this var, other than printing it + + start = monotonic.monotonic() daemon = Daemon() wallet = Wallet() # check info/height/balance before generating blocks res_info = daemon.get_info() - prev_height = res_info.height + initial_height = res_info.height res_getbalance = wallet.get_balance() prev_balance = res_getbalance.balance @@ -85,29 +104,75 @@ class MiningTest(): if via_daemon: res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1) else: - res = wallet.start_mining(threads_count = 1) + res = wallet.start_mining(threads_count = cores_mine) res_status = daemon.mining_status() assert res_status.active == True - assert res_status.threads_count == 1 + assert res_status.threads_count == cores_mine assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' assert res_status.is_background_mining_enabled == False assert res_status.block_reward >= 600000000000 # wait till we mined a few of them - target_height = prev_height + 5 - height = prev_height - timeout = 240 # randomx is slow to init + target_height = initial_height + 5 + height = initial_height + + if not is_mining_measurent: + timeout_init = 600 + timeout_mine = 300 + else: + """ + Randomx init has high variance on CI machines due to noisy neighbors, + taking up resources in parallel (including by our own jobs). + + Mining is organized in the following scheme: + 1) first loop's pass: RandomX init and mining + 2) every next pass: only mining + Pass 1) takes much more time than pass 2) + Pass 1) uses all cores, pass 2) just one (currently) + For the above reasons both passes need separate timeouts and adjustments. + After the first pass, the timeout is being reset to a lower value. + """ + + def calc_timeout(seconds_constant, time_pi, cores): + """ + The time it took to calculate pi under certain conditions + is proportional to the time it will take to calculate the real job. + + The number of cores used decreases the time almost linearly. + """ + timeout = float(seconds_constant) * time_pi / float(cores) + return timeout + + timeout_base_init = 60 # RX init needs more time + timeout_base_mine = 20 + timeout_init = calc_timeout(timeout_base_init, time_pi_all_cores, cores_init) + timeout_mine = calc_timeout(timeout_base_mine, time_pi_single_cpu, cores_mine) + + msg_timeout_src = "adjusted for the currently available CPU power" if is_mining_measurent else "selected to have the default value" + msg = "Timeout for {} {}, is {:.1f} s" + self.print_mining_info(msg.format("init, ", msg_timeout_src, timeout_init)) + self.print_mining_info(msg.format("mining,", msg_timeout_src, timeout_mine)) + timeout = timeout_init + rx_inited = False # Gets initialized in the first pass of the below loop while height < target_height: seen_height = height - for _ in range(timeout): + for _ in range(int(math.ceil(timeout))): time.sleep(1) + seconds_passed = monotonic.monotonic() - start height = daemon.get_info().height if height > seen_height: break else: - assert False, 'Failed to mine successor to block %d (initial block = %d)' % (seen_height, prev_height) - timeout = 10 + assert False, 'Failed to mine successor to block %d (initial block = %d) after %d s. RX initialized = %r' % (seen_height, initial_height, round(seconds_passed), rx_inited) + if not rx_inited: + rx_inited = True + timeout = timeout_mine # Resetting the timeout after first mined block and RX init + self.print_time_taken(start, "RX init + mining 1st block") + else: + self.print_time_taken(start, "mining iteration") + + self.print_time_taken(start, "mining total") if via_daemon: res = daemon.stop_mining() @@ -123,7 +188,7 @@ class MiningTest(): wallet.refresh() res_getbalance = wallet.get_balance() balance = res_getbalance.balance - assert balance >= prev_balance + (new_height - prev_height) * 600000000000 + assert balance >= prev_balance + (new_height - initial_height) * 600000000000 if via_daemon: res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1, do_background_mining = True) @@ -144,6 +209,23 @@ class MiningTest(): res_status = daemon.mining_status() assert res_status.active == False + def measure_cpu_power_get_time(self, cores): + self.print_mining_info("Measuring the currently available CPU power...") + build_dir_funcional_tests = os.environ['FUNCTIONAL_TESTS_DIRECTORY'] + time_pi = util_resources.get_time_pi_seconds(cores, build_dir_funcional_tests) + self.print_mining_info("Time taken to calculate Pi on {} core(s) was {:.2f} s.".format(cores, time_pi)) + return time_pi + + def get_available_ram(self): + available_ram = util_resources.available_ram_gb() + threshold_ram = 3 + self.print_mining_info("Available RAM = " + str(round(available_ram, 1)) + " GB") + if available_ram < threshold_ram: + print("Warning! Available RAM =", round(available_ram, 1), + "GB is less than the reasonable threshold =", threshold_ram, + ". The RX init might exceed the calculated timeout.") + return available_ram + def submitblock(self): print("Test submitblock") @@ -171,6 +253,20 @@ class MiningTest(): assert res.height == height + i + 1 assert res.hash == block_hash + def is_mining_silent(self): + return 'MINING_SILENT' in os.environ + + def print_mining_info(self, msg): + if self.is_mining_silent(): + return + print(msg) + + def print_time_taken(self, start, msg_context): + if self.is_mining_silent(): + return + seconds_passed = monotonic.monotonic() - start + print("Time taken for", msg_context, "=", round(seconds_passed, 1), "s.") + def test_randomx(self): print("Test RandomX") |