[CI] Adaptive mining timeout, depending on available CPU power

Printing also available RAM. Add comprehensive description.
This commit is contained in:
mj-xmr 2021-02-18 23:30:41 +01:00
parent a7663f4ba1
commit 45f01f577c
6 changed files with 256 additions and 12 deletions

View file

@ -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
@ -71,12 +75,20 @@ 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
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 +97,71 @@ 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
"""
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 for {} adjusted for the currently available CPU power, is {:.1f} s"
print(msg.format("init, ", timeout_init))
print(msg.format("mining,", 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 +177,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)
@ -143,6 +197,22 @@ class MiningTest():
res = wallet.stop_mining()
res_status = daemon.mining_status()
assert res_status.active == False
def measure_cpu_power_get_time(self, cores):
print("Measuring the currently available CPU power...")
time_pi = util_resources.get_time_pi_seconds(cores)
print("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
print("Available RAM =", 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")
@ -170,6 +240,10 @@ class MiningTest():
res = daemon.get_height()
assert res.height == height + i + 1
assert res.hash == block_hash
def print_time_taken(self, start, msg_context):
seconds_passed = monotonic.monotonic() - start
print("Time taken for", msg_context, "=", round(seconds_passed, 1), "s.")
def test_randomx(self):
print("Test RandomX")