backend.when_monitorminer.domonitorminer()   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 32
nop 1
dl 0
loc 45
rs 7.2453
c 0
b 0
f 0
1
'''#David Foderick, Skylake Software Inc.
2
#Runs behind firewall
3
'''
4
import datetime
5
from threading import Thread
6
from queue import Queue
7
from colorama import Fore
8
import pika
9
from helpers.antminerhelper import MinerMonitorException, stats
10
from helpers.queuehelper import QueueName, QueueEntries
11
from domain import mining
12
from backend.fcmapp import Component
13
14
APPMONITOR = Component('monitorminer')
15
MONITOR_PREFETCH = int(APPMONITOR.app.configuration.get("monitoring.queue.prefetch"))
16
17
def enthread(target, args):
18
    '''put a method on a queue to be run in background'''
19
    thread_queue = Queue()
20
    def wrapper():
21
        thread_queue.put(target(*args))
22
    thread = Thread(target=wrapper)
23
    thread.start()
24
    return thread_queue
25
26
def when_monitorminer(channel, method, properties, body):
27
    try:
28
        print("[{0}] Received monitorminer command".format(APPMONITOR.app.now()))
29
        minermsg = APPMONITOR.app.messagedecodeminer(body)
30
        #monitor in background so pika socket doesnt get messed up
31
        qmon = enthread(target=domonitorminer, args=(minermsg, ))
32
        APPMONITOR.app.enqueue(qmon.get())
33
    except Exception as ex:
34
        APPMONITOR.app.logexception(ex)
35
36
def domonitorminer(miner):
37
    '''get statistics from miner'''
38
    entries = QueueEntries()
39
    savedminer = APPMONITOR.app.getminer(miner)
40
    if savedminer is None:
41
        savedminer = miner
42
    try:
43
        #individual miner can be monitored even if manually disabled
44
        #todo:savedminer and knownminer out of sync. this will be fixed in refactoring redis
45
        if not savedminer.should_monitor() and not miner.should_monitor():
46
            print('skipped monitoring {0}'.format(miner.name))
47
            return entries
48
        mineroriginalstatus = savedminer.status
49
        minerstats, minerinfo, apicall, minerpool = stats(savedminer)
50
        #minerlcd = antminerhelper.getminerlcd(miner)
51
        if minerstats is None:
52
            print('could not monitor {0}({1})'.format(savedminer.name, savedminer.ipaddress))
53
        else:
54
            process_stats(entries, savedminer, mineroriginalstatus, minerstats, minerpool, minerinfo, apicall)
55
56
    except pika.exceptions.ConnectionClosed as qex:
57
        #could not enqueue a message
58
        print(Fore.RED + '{0} Queue Error: {1}'.format(savedminer.name,
59
                                                       APPMONITOR.app.exceptionmessage(qex)))
60
        APPMONITOR.app.logexception(qex)
61
    except MinerMonitorException as monitorex:
62
        print(Fore.RED + '{0} Miner Error: {1}'.format(savedminer.name,
63
                                                       APPMONITOR.app.exceptionmessage(monitorex)))
64
        savedminer.lastmonitor = datetime.datetime.utcnow()
65
        #TODO: this should be a rule. publish miner offline event
66
        #and let event handler decide how to handle it
67
        savedminer.offline_now()
68
        print(Fore.RED + APPMONITOR.app.now(), savedminer.name, savedminer.status)
69
        entries.add(QueueName.Q_OFFLINE, APPMONITOR.app.messageencode(savedminer))
70
71
    except BaseException as ex:
72
        print(Fore.RED+'{0} Unexpected Error in monitorminer: {1}'.format(savedminer.name,
73
                                                                          APPMONITOR.app.exceptionmessage(ex)))
74
        # we have to consider any exception to be a miner error. sets status to offline
75
        #if str(e) == "timed out": #(timeout('timed out',),)
76
        APPMONITOR.app.logexception(ex)
77
    #TODO: review usage of savedminer and knownminer. should only go with one
78
    APPMONITOR.app.putminer(savedminer)
79
    APPMONITOR.app.updateknownminer(savedminer)
80
    return entries
81
82
def process_stats(entries, savedminer, mineroriginalstatus, minerstats, minerpool, minerinfo, apicall):
83
    #what to do if monitored miner type conflicts with saved miner type???
84
    #should probably provision?
85
    foundpool = APPMONITOR.app.pools.findpool(minerpool)
86
    if foundpool is not None:
87
        minerpool.poolname = foundpool.name
88
    savedminer.monitored(minerstats, minerpool, minerinfo, apicall.elapsed())
89
    if mineroriginalstatus == '':
90
        #first time monitoring since bootup
91
        print(Fore.GREEN + APPMONITOR.app.now(), savedminer.name, 'first time monitoring')
92
    elif savedminer.status == mining.MinerStatus.Online and (mineroriginalstatus == mining.MinerStatus.Disabled or mineroriginalstatus == mining.MinerStatus.Offline):
93
        #changing status from offline to online so raise event
94
        entries.add(QueueName.Q_ONLINE, APPMONITOR.app.messageencode(savedminer))
95
        print(Fore.GREEN + APPMONITOR.app.now(), savedminer.name, 'back online!')
96
    #TODO: if stats.elapsed < previous.elapsed then raise provision or online events
97
98
    APPMONITOR.app.putminerandstats(savedminer, minerstats, minerpool)
99
    #show name of current pool instead of worker
100
    print('{0} mining at {1}'.format(savedminer.name, getpoolname(minerpool)))
101
    check_miner_should_provision(entries, savedminer, minerpool)
102
103
    print(Fore.CYAN+str(APPMONITOR.app.now()), savedminer.name, savedminer.status,
104
          'h='+str(minerstats.currenthash), str(minerstats.minercount),
105
          '{0}/{1}/{2}'.format(str(minerstats.tempboard1),
106
                               str(minerstats.tempboard2),
107
                               str(minerstats.tempboard3)),
108
          savedminer.uptime(minerstats.elapsed),
109
          '{0:d}ms'.format(int(savedminer.monitorresponsetime() * 1000)))
110
    msg = APPMONITOR.app.createmessagestats(savedminer, minerstats, minerpool)
111
    entries.addbroadcast(QueueName.Q_STATISTICSUPDATED, msg)
112
113
def check_miner_should_provision(entries, savedminer, minerpool):
114
    #most users won't want to mine solo, so provision the miner
115
    if not APPMONITOR.app.configuration.get('mining.allowsolomining'):
116
        if not minerpool.currentpool or minerpool.currentpool.startswith(APPMONITOR.app.configuration.get('mining.solopool')):
117
            entries.add(QueueName.Q_PROVISION, APPMONITOR.app.messageencode(savedminer))
118
119
def getpoolname(minerpool):
120
    poolname = '?'
121
    if minerpool:
122
        poolname = '{0} {1}'.format(minerpool.currentpool, minerpool.currentworker)
123
    foundpool = APPMONITOR.app.pools.findpool(minerpool)
124
    if foundpool is not None:
125
        poolname = foundpool.name
126
    return poolname
127
128
def main():
129
    '''main'''
130
    if APPMONITOR.app.isrunnow or APPMONITOR.app.isdebug:
131
        test_miner = APPMONITOR.app.getknownminerbyname('192.168.1.177')
132
        if not test_miner:
133
            test_miner = APPMONITOR.app.getknownminerbyname('#S9000')
134
        if test_miner:
135
            domonitorminer(test_miner)
136
        APPMONITOR.app.shutdown()
137
    else:
138
        APPMONITOR.listeningqueue = APPMONITOR.app.subscribe(QueueName.Q_MONITORMINER, when_monitorminer, MONITOR_PREFETCH)
139
        APPMONITOR.listen()
140
141
if __name__ == "__main__":
142
    main()
143