-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathauto-pilot.py
339 lines (313 loc) · 13.4 KB
/
auto-pilot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
import configparser
import os, time, subprocess, shlex
import requests, json, urllib
import psutil
import errno, signal
ALGO_SHORTNAMES = {
"ethash":"eth","groestl":"gro","phi1612":"phi","cryptonight":"cn",
"cryptonightv7":"cn7","equihash":"eq","lyra2rev2":"lre","neoscrypt":"ns",
"timetravel10":"tt10","x16r":"x16r","skunkhash":"skh","nist5":"n5","xevan":"xn"}
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
#BASE_DIR = os.environ['HOME']
CFG_PATH = os.path.join(BASE_DIR,"input.cfg")
config = configparser.ConfigParser()
config.read(CFG_PATH)
current_algo = ''
current_perf24h = 0.0
# handle to the currently running proc
miner_proc = None
miner_pid = 0
retries = 0
def write_content(section,key,val):
#config[section][key]= str(val)
with open(os.path.join(BASE_DIR,"internal.cfg"),'w') as configfile:
configfile.write(str(val))
def get_content(section, key,d='20'):
value = d
try:
value = config.get(section, key,raw=True)
except:
value = d
return value
def get_section(section):
ret = None
try:
ret = config[section]
except:
ret = None
return ret
def get_uri():
uri = "https://whattomine.com/coins.json?utf8=%E2%9C%93"
gpu_cards = get_section("gpus")
# card names do not get case sensitivized!!
for gpu in gpu_cards:
str3 = "&adapt_q_"+gpu+"="+get_content("gpus",gpu)
uri = uri + str3
# This block creates the power and hash rate section of params
algorithms = get_section("algorithms")
#print("[AutoSwitch] "+type(algorithms))
for algo in algorithms:
algo = str(algo).lower()
if get_content("algorithms",algo) == "1":
# print("[AutoSwitch] "+"Algo ",algo," is enabled")
algo_code = ALGO_SHORTNAMES[algo]
str2 = "&" + algo_code +"=true"
if algo_code == 'lre':
algo_code = algo_code + 'v2'
str1 = "&factor%5B"+algo_code+"_hr%5D="+get_content(algo,"hash-rate","20")+"&factor%5B"+algo_code+"_p%5D="+get_content(algo,"power","90")
# print("[AutoSwitch] "+str2)
# print("[AutoSwitch] "+str1)
uri = str(uri+str2)
uri = str(uri+str1)
uri = uri + "&factor%5Bcost%5D="+get_content("general","power_cost_per_kwh")
#print("cost:",get_content("params","power_cost_per_kwh"))
str4 = "&sort=Profitability24&volume=0&revenue=24h&factor%5Bexchanges%5D%5B%5D=&factor%5Bexchanges%5D%5B%5D=binance&factor%5Bexchanges%5D%5B%5D=bitfinex&\
factor%5Bexchanges%5D%5B%5D=bittrex&factor%5Bexchanges%5D%5B%5D=cryptobridge&\
factor%5Bexchanges%5D%5B%5D=cryptopia&factor%5Bexchanges%5D%5B%5D=hitbtc&\
factor%5Bexchanges%5D%5B%5D=poloniex&factor%5Bexchanges%5D%5B%5D=yobit&\
dataset=Main&commit=Calculate"
uri = uri + str4
#print("[AutoSwitch] "+"Till now URI:",uri)
return uri
from collections import OrderedDict
def get_performance(coins_dict):
unsorted_list = {}
algo=''
key=''
for item in coins_dict:
if item == 'Nicehash-Equihash' or item == 'Nicehash-Ethash':
key= item
else:
algo=coins_dict[item]['algorithm']
key = item + '-'+algo
unsorted_list[key]=format(float(coins_dict[item]['btc_revenue24']),'.8f')
print("[AutoSwitch] "+"Algorithm:",key," profitability:",unsorted_list[key]) #coins_dict[item]['btc_revenue24'])
sorted_list = OrderedDict(sorted(unsorted_list.items(),key=lambda t:t[1]))
#print("[AutoSwitch] "+"Sorted:\n",len(sorted_list))
# for i in range(len(sorted_list)):
# print("[AutoSwitch] "+sorted_list.popitem(last=True)[0])
return sorted_list
def get_from_whattomine():
uri = get_uri()
print("[AutoSwitch] "+"URL:",uri)
#get_coin_algo_list(data)
#data = json.load(urllib.request.urlopen(uri))
r = requests.get(uri)
data = r.json()
#print("[AutoSwitch] "+data['coins'])
performers = get_performance(data['coins'])
return performers
def change_miner(new_algo,new_perf24h):
change_trigger = False
print("[AutoSwitch] "+"been asked to change algo to :",new_algo," with 24hr profitability:",str(new_perf24h))
global current_algo, current_perf24h, miner_proc
# check if miner proc has children - which will mean miner is running
if miner_proc != None and pid_exists(miner_proc.pid):
change_trigger = False
else:
change_trigger = True
if current_algo != new_algo or change_trigger:
# a newer algo is suggested to be fired
new_miner = get_content("miners",new_algo)
#miner_file = os.path.join(os.path.dirname(BASE_DIR),new_miner)
miner_file = os.path.join(BASE_DIR,new_miner)
if miner_proc != None:
kill_proc(miner_proc)
#cmd = "xfce4-terminal --command="+miner_file+" --working-directory="+os.path.dirname(BASE_DIR)
cmd = miner_file
#try:
miner_proc = subprocess.Popen(['bash',cmd]) #,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#except:
# print("[AutoSwitch] "+"Process exited or errored")
print("[AutoSwitch] "+"Miner switched from:",current_algo,"with 24h:",str(current_perf24h)," to:",new_algo,":",str(new_perf24h)," with proc ID:",miner_proc.pid)
current_algo = new_algo
current_perf24h = new_perf24h
write_content("internal","id",miner_proc.pid) #this PID will be killed at startup
else:
#print("[AutoSwitch] "+"Current algo:",current_algo," same as suggested new algo:",new_algo," So no change")
pass
return miner_proc
def kill_proc(proc):
try:
#print("[AutoSwitch] "+"kill request for:",proc.pid)
if pid_exists(proc.pid):
p = psutil.Process(proc.pid)
p_child = p.children(recursive=True)
#print("[AutoSwitch] "+"need to kill:",p_child)
for child in p_child:
#print("[AutoSwitch] "+"killing children:",child.pid)
subprocess.call(['kill','-9',str(child.pid)])
#print("[AutoSwitch] "+"killing main:",proc.pid)
subprocess.call(['kill','-9',str(proc.pid)])
except:
print("[AutoSwitch] "+"exception in kill_proc, likely no proc found for:",proc)
pass
return True
def pid_exists(pid):
"""Check whether pid exists in the current process table.
UNIX only.
"""
if pid < 0:
return False
if pid == 0:
# According to "man 2 kill" PID 0 refers to every process
# in the process group of the calling process.
# On certain systems 0 is a valid PID but we have no way
# to know that in a portable fashion.
raise ValueError('invalid PID 0')
try:
os.kill(pid, 0)
except OSError as err:
if err.errno == errno.ESRCH:
# ESRCH == No such process
return False
elif err.errno == errno.EPERM:
# EPERM clearly means there's a process to deny access to
return True
else:
# According to "man 2 kill" possible error values are
# (EINVAL, EPERM, ESRCH)
raise
else:
return True
def startup():
ret = False
try:
if os.path.isfile(os.path.join(BASE_DIR,"internal.cfg")):
with open(os.path.join(BASE_DIR,"internal.cfg"),'r') as configfile:
txt = configfile.readline()
pid = int(txt)
print("[AutoSwitch] "+"found process to be killed:",txt," type:",type(txt))
if pid_exists(pid):
p = psutil.Process(pid)
if p.isRunning():
ret = kill_proc(p)
#subprocess.call(['kill',txt])
ret = True
except:
print("[AutoSwitch] "+"No evidance of prev. run")
ret = True
if os.path.isfile(os.path.join(BASE_DIR,"internal.cfg")):
os.remove(os.path.join(BASE_DIR,"internal.cfg"))
return ret
# Will return PASS if no change required, RESTART if new miner proc not Found
# and will return START_DEFAULT if retry attempts exceeded, so start default miner
def check_for_stalled_miner(proc):
global retries,miner_proc
ret_flag = "PASS" # indicates no need to change miner
if proc != None and pid_exists(proc.pid):
p = psutil.Process(proc.pid)
proc_children = p.children(recursive=True)
if len(proc_children) > 0:
# The miner is running, do nothing and exit
print("[AutoSwitch] "+" Currently mining:",new_algo)
else:
# kill pid and start new miner
ret = kill_proc(proc)
if retries > 3:
# after 3 failed attempts try changing to default miner
m = "default-miner"
print("[AutoSwitch] "+"Miner process not detected. Switching to default miner.")
#miner_proc = change_miner(m,0.00010)
retries = 0
ret_flag = "START_DEFAULT"
else:
print("[AutoSwitch] "+"Miner process not detected. Attempting to restart miner:",new_algo)
#miner_proc = change_miner(new_algo,float(new_perf24h))
retries += 1
ret_flag = "RESTART"
else:
if retries > 3:
# after 3 failed attempts try changing to default miner
print("[AutoSwitch] "+"No miner detected. Switching to default miner.")
m = "default-miner"
retries = 0
#miner_proc = change_miner(m,0.00010)
ret_flag = "START_DEFAULT"
else:
print("[AutoSwitch] "+"No miner detected. Attempting to restart miner:",new_algo)
#miner_proc = change_miner(new_algo,float(new_perf24h))
retries += 1
ret_flag = "RESTART"
return ret_flag
if __name__ == '__main__':
global current_algo, miner_proc
#performers = get_from_whattomine()
if startup() == False:
print("[AutoSwitch] "+"Another miner instance seems to be running. Please terminate it and try again.")
exit()
polling_frequency = get_content("general","polling_frequency")
preferred_perf_threshold = get_content("general","threshold")
miners_available = get_section("miners")
new_algo=''
new_perf24h = 0.0
while True:
performers = get_from_whattomine()
matchFound = False
for i in range(len(performers)):
#print("[AutoSwitch] "+performers.popitem(last=True))
topper = performers.popitem(last=True)
print("[AutoSwitch] "+"Most profitable is:",str(topper[0]))
for miner in miners_available:
#print("[AutoSwitch] "+"miner:",miner," type:",type(miner)," Tpper:",topper[0])
if str(topper[0]).lower() == miner:
#print("[AutoSwitch] "+"Topper match found")
matchFound = True
elif miner in str(topper[0]).lower():
#print("[AutoSwitch] "+"Miner :",miner," part of:",topper)
matchFound = True
if matchFound:
new_algo = miner
new_perf24h = float(topper[1])
break
#print("[AutoSwitch] "+"exited miner loop..miner found")
if matchFound:
break
#print("[AutoSwitch] "+"Exited Top performers loop..miner found")
# exit the main for loop as matching miner was found
if matchFound == True:
if new_algo == current_algo:
# There will be no switch, just update the revised perf_rate
current_perf24h = float(new_perf24h)
perf_diff = 0
elif current_perf24h != 0.0:
perf_x = (new_perf24h - current_perf24h)/current_perf24h
#print("[AutoSwitch] perf_x:",perf_x," current:",current_perf24h," new:",new_perf24h)
perf_diff = float(perf_x) - float(preferred_perf_threshold)
print("[AutoSwitch] "+"Perf difference:",perf_diff)
else:
perf_diff = new_perf24h
#print("[AutoSwitch] "+"Perf difference NOT calculated:",perf_diff)
# if difference is greater than threshold, need to switch
if perf_diff > 0:
miner_proc = change_miner(new_algo,float(new_perf24h))
# else let mining continue as is
elif matchFound == False:
if current_algo == '':
# that means not running and no match found, so start default miner
default_miner = miners_available["default-miner"]
print("[AutoSwitch] "+"No prioritized miner found. Trying default miner:","default-miner")
# it is not currently mining, so just get top miner and start
m = "default-miner"
miner_proc = change_miner(m,0.00010)
if polling_frequency is None:
polling_frequency = 600
step_counter = 60
i=0
while True:
if i==0:
time.sleep(120)
else:
time.sleep(step_counter)
i += step_counter
if i > int(polling_frequency):
break
else:
flag = check_for_stalled_miner(miner_proc)
if flag == "RESTART":
miner_proc = change_miner(new_algo,float(new_perf24h))
elif flag == "START_DEFAULT":
miner_proc = change_miner(m,0.00010)
#time.sleep(int(polling_frequency))
#print("[AutoSwitch] "+"Will sleep for :", polling_frequency)
#print("[AutoSwitch] "+"Done") # this should never get printed