It works!

This commit is contained in:
2018-01-21 19:44:54 -08:00
parent 055ab45efd
commit b73ab72e0a
3 changed files with 159 additions and 50 deletions

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright © 2017 Scott Alfter
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,20 +1,21 @@
zpool auto-switcher
===================
TODO: documentation...still early days, but should be similar to my NiceHash
and MiningPoolHub switchers.
This is an auto-switcher for zpool (www.zpool.ca) that chooses the most
profitable coin based on the statistics provided by zpool and the hashrates
supported by your GPUs. It's similar to my MiningPoolHub and NiceHash
auto-switchers, and is set up similarly, except that configuration is
somewhat more modularized. In the future, the other switchers will change
to a similar configuration so that it will be possible to share most of your
configuration between the different pools.
zpool's API is largely undocumented. A further complication is that their
profitability estimates aren't reported in the same units for each
algorithm, and while some deviations from the norm are noted on their
homepage, others aren't. I'm mostly going by trial and error, and by
comparison with what MiningPoolHub reports for the algorithms the two pools
have in common.
I started this under nvOC, but have since migrated my mining rig from nvOC
to a standard Gentoo Linux setup.
miners.json
===========
Should ultimately be able to share this with the MiningPoolHub and NiceHash
We should ultimately be able to share this with the MiningPoolHub and NiceHash
switchers. Each available algorithm is described with the following values:
bin: command line to launch miner. {HOST}, {PORT}, {USERNAME}, and
@@ -29,6 +30,8 @@ power: total power consumption, kW
gpu_oc, mem_oc, and fan may be provided as a single value each to be applied
to all cards or as an array each to set cards individually.
Any additional fields (such as last_benchmark) are ignored.
algo_map.json
=============
@@ -36,6 +39,9 @@ Since different pools may use different labels for the same algorithm, we
need to map what the pool uses to what we used in miners.json. The index is
the pool's label; the value is our label.
When zpool adds a new algorithm, this file is automatically updated with an
empty entry for the new algorithm.
conf.json
=========
@@ -51,4 +57,6 @@ payment_currency: how you want zpool to pay you
payment_addr: where zpool should send payment
The user_name and miner_name values in the example aren't used by the zpool
switcher, but may be used by other switchers.
switcher, but may be used by other switchers. As with miners.json, we might
be able to share this file with other pool auto-switchers.

View File

@@ -1,33 +1,25 @@
#!/usr/bin/env python3
# API description: http://www.zpool.ca/site/api
# estimates: values in mBTC/MH/day,
# mBTC/PH/day for sha256
# mBTC/GH/day for scrypt, blake, decred, x11, quark, qubit
# mBTC/kS/day for equihash
# API description (such as it is): http://www.zpool.ca/site/api
import pprint
import json
import urllib.request
import urllib.parse
import sys
import datetime
import time
import subprocess
import os
import socket
import urllib.request
import urllib.parse
# load config
DEBUG=False
cfg=json.loads(open(sys.argv[1]).read())
miners=json.loads(open(sys.argv[2]).read())
algo_map=json.loads(open(sys.argv[3]).read())
card_type=cfg["card_type"]
#user_name=cfg["user_name"]
#miner_name=cfg["miner_name"]
currency=cfg["currency"]
pwrcost=cfg["pwrcost"]
min_profit=cfg["min_profit"]
@@ -36,29 +28,20 @@ payment_addr=cfg["payment_addr"]
os.environ["DISPLAY"]=":0"
# IPv4 address lookup
def addr(host):
return [addr[4][0] for addr in socket.getaddrinfo(host, None) if addr[0] == socket.AF_INET][0]
# grab something from a website
def fetch(prot, host, path, forceipv4=False):
if (forceipv4):
url=prot+"://"+addr(host)+"/"+path
else:
url=prot+"://"+host+"/"+path
r=urllib.request.Request(url, None, {"User-Agent": "Lynx/2.8.8dev.3 libwww-FM/2.14 SSL-MM/1.4.1", "Pragma": "no-cache", "Host": host})
def fetch(url):
r=urllib.request.Request(url, None, {"User-Agent": "Lynx/2.8.8dev.3 libwww-FM/2.14 SSL-MM/1.4.1", "Pragma": "no-cache"})
return urllib.request.urlopen(r).read().decode("utf-8")
# main
if (DEBUG!=True):
exchrate=float(json.loads(fetch("https", "api.coinbase.com", "/v2/exchange-rates?currency=BTC"))["data"]["rates"][currency])
data=json.loads(fetch("http", "www.zpool.ca", "/api/status", True))
else:
exchrate=float(json.loads(open("dbgdata-exchange-rates").read())["data"]["rates"][currency])
data=json.loads(open("dbgdata-status").read())
try:
exchrate=float(json.loads(fetch("https://api.coinbase.com/v2/exchange-rates?currency=BTC"))["data"]["rates"][currency])
data=json.loads(fetch("http://www.zpool.ca/api/status"))
except:
print("unable to retrieve remote data", file=sys.stderr)
sys.exit(-1)
# update algo map
@@ -74,23 +57,36 @@ if (changed==True):
with open(sys.argv[3], "w") as outfile:
json.dump(algo_map, outfile, sort_keys=True, indent=2)
# adjust estimates so they're all in the same units: BTC/day per GH/s
# weed out miners not supported by the pool
filtered_miners={}
for i in data:
data[i]["adjusted_estimate"]=float(data[i]["estimate_current"])*1000
try:
filtered_miners[algo_map[i]]=miners[algo_map[i]]
except:
pass
miners=filtered_miners
# adjust estimates so they're all in the same units: BTC/day per GH/s
for i in data:
data[i]["estimate_current"]=float(data[i]["estimate_current"])*1000
try:
data["sha256"]["adjusted_estimate"]/=1000000000000
data["sha256"]["estimate_current"]/=1000000000
except:
pass
try:
data["equihash"]["adjusted_estimate"]*=1000
data["equihash"]["estimate_current"]*=1000
except:
pass
for i in ["scrypt", "blakecoin", "blake2s", "decred", "x11", "quark", "qubit", "keccak"]:
try:
data[i]["adjusted_estimate"]/=1000
data[i]["estimate_current"]/=1000
except:
pass
# calculate profitability for our hardware
coins={}
for i in data:
if (algo_map[i]!=""):
@@ -99,13 +95,99 @@ for i in data:
coins[i]["algo"]=i
coins[i]["mapped_algo"]=algo_map[i]
coins[i]["name"]=i
coins[i]["estimate"]=data[i]["adjusted_estimate"]*float(coins[i]["speed"]) # factor in our speed
for i in data:
print(i+": "+str(data[i]["estimate_current"])+" "+str(data[i]["adjusted_estimate"]))
print("")
coins[i]["estimate"]=data[i]["estimate_current"]*float(coins[i]["speed"])-24.0*coins[i]["power"]*pwrcost/exchrate
# sort by profitability
sort={}
for i in coins:
print(i+": "+str(coins[i]["estimate"]))
sort[i]=coins[i]["estimate"]
sort=sorted(sort.items(), key=lambda x:x[1], reverse=True)
log=open("current-profit", "w")
for i in sort:
log.write(i[0]+": "+format(i[1], ".8f")+" BTC/day ("+format(i[1]*exchrate, ".2f")+" "+currency+"/day)\n")
log.close()
miner=coins[sort[0][0]]
if (len(sys.argv)==4):
# exit if maximum is below minimum
if (miner["estimate"]<min_profit):
algo_log=open("algo-log", "a")
algo_log.write(str(datetime.datetime.now())+": **NONE**\n")
algo_log.close()
for algo in coins:
subprocess.call(["pkill", "-f", "^"+coins[algo].replace("+", "\\+")])
sys.exit()
else: # manual override
if (sys.argv[4]!="list"):
miner=coins[sys.argv[4]]
else: # list available algos
print("algos: ", end="")
for i in coins:
print(i+" ", end="")
print("")
sys.exit()
# see if miner's already running
try:
subprocess.check_output(["pgrep", "-f", "^"+miner["bin"].replace("+", "\\+")])
current=True
except:
current=False
other=False;
if (current==False):
for algo in miners:
try:
subprocess.check_output(["pgrep", "-f", "^"+miners[algo]["bin"].replace("+", "\\+")])
other=True
except:
pass
if (current==False):
# log a change
algo_log=open("algo-log", "a")
algo_log.write(str(datetime.datetime.now())+": * ("+miner["algo"]+") "+format(miner["estimate"], ".8f")+" "+format(miner["estimate"]*exchrate, ".2f")+"\n")
algo_log.close()
# kill existing miners
for algo in miners:
subprocess.call(["pkill", "-f", "^"+miners[algo]["bin"].replace("+", "\\+")])
time.sleep(3)
if (card_type=="nvidia"): # update card settings
cards=int(subprocess.check_output("nvidia-smi --query-gpu=count --format=csv,noheader,nounits".split(" ")).decode("utf-8").split("\n")[-2])
for i in range(0, cards):
# power limit
if type(miner["power_limit"]) is int:
subprocess.call(("sudo nvidia-smi -i "+str(i)+" -pl "+str(miner["power_limit"])).split(" "))
else:
subprocess.call(("sudo nvidia-smi -i "+str(i)+" -pl "+str(miner["power_limit"][i])).split(" "))
# core overclock
if type(miner["gpu_oc"]) is int:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUGraphicsClockOffset[2]="+str(miner["gpu_oc"])).split(" "))
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUGraphicsClockOffset[3]="+str(miner["gpu_oc"])).split(" "))
else:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUGraphicsClockOffset[2]="+str(miner["gpu_oc"][i])).split(" "))
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUGraphicsClockOffset[3]="+str(miner["gpu_oc"][i])).split(" "))
# memory overclock
if type(miner["mem_oc"]) is int:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUMemoryTransferRateOffset[2]="+str(miner["mem_oc"])).split(" "))
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUMemoryTransferRateOffset[3]="+str(miner["mem_oc"])).split(" "))
else:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUMemoryTransferRateOffset[2]="+str(miner["mem_oc"][i])).split(" "))
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUMemoryTransferRateOffset[3]="+str(miner["mem_oc"][i])).split(" "))
# fan speed
if type(miner["fan"]) is int:
if (miner["fan"]==0):
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUFanControlState=0").split(" "))
else:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUFanControlState=1").split(" "))
subprocess.call(("nvidia-settings -a [fan:"+str(i)+"]/GPUTargetFanSpeed="+str(miner["fan"])).split(" "))
else:
if (miner["fan"][i]==0):
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUFanControlState=0").split(" "))
else:
subprocess.call(("nvidia-settings -a [gpu:"+str(i)+"]/GPUFanControlState=1").split(" "))
subprocess.call(("nvidia-settings -a [fan:"+str(i)+"]/GPUTargetFanSpeed="+str(miner["fan"][i])).split(" "))
# launch new miner
subprocess.call(("screen -dmS miner "+miner["bin"]).split(" "))