Passed
Push — master ( 4ed8d2...9f74b6 )
by Dave
58s
created

backend.when_monitorminer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 106
dl 0
loc 144
rs 10
c 0
b 0
f 0
wmc 29

7 Functions

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