Passed
Push — master ( 933317...73c77f )
by Dave
01:02
created

domain.mining.Miner.__init__()   A

Complexity

Conditions 1

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
nop 19
dl 0
loc 38
rs 9.328
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
'''#Full Cycle Mining Domain'''
2
import time
3
from datetime import datetime, timezone
4
5
class MinerStatus:
6
    '''Status of Miner'''
7
    Online = 'online'
8
    Offline = 'offline'
9
    Disabled = 'disabled'
10
11
class MinerAccessLevel:
12
    Restricted = 'restricted'
13
    Privileged = 'priviledged'
14
    #not really a level, waiting for access upgrade
15
    Waiting = 'waiting'
16
17
class Login(object):
18
    """Login name and password for access to miners"""
19
    def __init__(self, username, password):
20
        self.username = username
21
        self.password = password
22
23
class MinerInfo(object):
24
    '''Meta information about a miner
25
    type and algo
26
    '''
27
    def __init__(self, miner_type, minerid):
28
        self.miner_type = miner_type
29
        self.minerid = minerid
30
31
class MinerCommand(object):
32
    """Command that could be sent to a miner"""
33
    def __init__(self, command='', parameter=''):
34
        self.command = command
35
        self.parameter = parameter
36
37
class Miner(object):
38
    """Miner"""
39
40
    def __init__(self, name, status=MinerStatus.Online, miner_type='', ipaddress='', port='', ftpport='', username='', password='', clientid='', networkid='', minerid='',
41
                 lastmonitor=None, offlinecount=0, defaultpool='', minerinfo=None, minerpool=None, minerstats=None, laststatuschanged=None):
42
        #friendly name for your miner
43
        self.name = name
44
        self._status = status
45
        #saved or derived from monitoring? type of miner. Antminer S9, Antminer D3, etc.
46
        self.miner_type = miner_type
47
        #ip address, usuall will be local ip address. example: 192.168.x.y
48
        self.ipaddress = ipaddress
49
        #ip port, usually will be 4028
50
        self.port = port
51
        self.ftpport = ftpport
52
        self.username = username
53
        self.password = password
54
        #the mydevices clientid for device
55
        self.clientid = clientid
56
        #network identifier for miner. usually the macaddress
57
        self.networkid = networkid
58
        #so far have only seen Antminer S9 have the minerid from STATS command
59
        self.minerid = minerid
60
        #last time the miner was monitored
61
        self.lastmonitor = lastmonitor
62
        self.monitorcount = 0
63
        self.monitortime = 0
64
        #number of times miner is offline during this session
65
        self.offlinecount = offlinecount
66
        #name of the pool that the miner should default to when it is provisioned
67
        self.defaultpool = defaultpool
68
        #meta info on the miner. should be assigned during discovery and monitor
69
        self.minerinfo = minerinfo
70
        #MinerCurrentPool
71
        self.minerpool = minerpool
72
        #MinerStatistics
73
        self.minerstats = minerstats
74
        #status of the miner. online, offline,disabled etc
75
        self.laststatuschanged = laststatuschanged
76
        #store is where the object was stored. mem is for memcache.
77
        self.store = ''
78
79
    @property
80
    def status(self):
81
        return self._status
82
83
    @status.setter
84
    def status(self, value):
85
        if value != '' and value != MinerStatus.Online and value != MinerStatus.Offline and value != MinerStatus.Disabled:
86
            raise ValueError('Invalid miner status {0}'.format(value))
87
        if self._status != value:
88
            self.laststatuschanged = datetime.utcnow()
89
        self._status = value
90
91
    @property
92
    def pools_available(self):
93
        if self.minerpool is None:
94
            return None
95
        available = []
96
        if 'POOLS' in self.minerpool.allpools:
97
            jpools = self.minerpool.allpools['POOLS']
98
            for jpool in jpools:
99
                available.append(AvailablePool(pool_type=self.miner_type, named_pool=None, url=jpool['URL'], user=jpool['User'], priority=jpool['Priority']))
100
        return available
101
102
    #@property
103
    def key(self):
104
        '''cache key for this entity'''
105
        thekey = self.name
106
        if isvalid_minerid():
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable isvalid_minerid does not seem to be defined.
Loading history...
107
            thekey = self.minerid
108
        elif isvalid_networkid():
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable isvalid_networkid does not seem to be defined.
Loading history...
109
            thekey = str(self.networkid)
110
        elif isvalid_ipaddress:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable isvalid_ipaddress does not seem to be defined.
Loading history...
111
            thekey = '{0}:{1}'().format(self.ipaddress, self.port)
112
        return thekey
113
114
    def isvalid_minerid(self):
115
        return self.minerid is not None and self.minerid and self.minerid != 'unknown'
116
117
    def isvalid_networkid(self):
118
        self.networkid is not None and self.networkid and str(self.networkid) != '{}'
119
120
    def isvalid_ipaddress(self):
121
        self.ipaddress is not None and self.ipaddress
122
123
    def set_ftp_port(self, port):
124
        if self.ftpport is not None and self.ftpport: return
125
        self.ftpport = port
126
127
    #todo: move ui code out of entity
128
    def summary(self):
129
        #datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S%f%z')
130
        return '{0} {1} {2} {3}'.format(self.name, self.hash_or_offline(), self.formattime(self.lastmonitor), self.currentpoolname())
131
132
    def currentpoolname(self):
133
        if self.minerpool is None:
134
            return '?'
135
        #todo:look up pools here?
136
        return self.minerpool.poolname
137
138
    def hash_or_offline(self):
139
        '''hash or offline status of miner'''
140
        if self.status != MinerStatus.Online:
141
            return self.status
142
        if self.minerstats is None: return self.status
143
        return self.minerstats.stats_summary()
144
145
    #todo: move to appservice
146
    def utc_to_local(self, utc_dt):
147
        return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
148
149
    def formattime(self, ptime):
150
        '''format time'''
151
        if ptime is None:
152
            return ''
153
        if isinstance(ptime, datetime):
154
            return self.utc_to_local(ptime).strftime('%m-%d %H:%M:%S')
155
        stime = ptime
156
        if '.' in stime:
157
            stime = stime[0:stime.index('.') - 1]
158
        try:
159
            parsedtime = datetime.strptime(stime, '%Y-%m-%dT%H:%M:%S')
160
            return self.utc_to_local(parsedtime).strftime('%m-%d %H:%M:%S')
161
        except ValueError:
162
            return stime
163
164
    def uptime(self, seconds):
165
        minutes, _ = divmod(seconds, 60)
166
        hours, minutes = divmod(minutes, 60)
167
        days, hours = divmod(hours, 24)
168
        return "%dd%dh%02dm" % (days, hours, minutes)
169
170
    def is_disabled(self):
171
        if self.is_manually_disabled() or self.status == MinerStatus.Disabled:
172
            return True
173
        return False
174
175
    def is_manually_disabled(self):
176
        if self.name.startswith("#"):
177
            return True
178
        return False
179
180
    def can_monitor(self):
181
        if not self.ipaddress:
182
            return False
183
        if not self.port:
184
            return False
185
        return True
186
187
    def should_monitor(self):
188
        #always monitor at least once when fcm app starts up
189
        if self.lastmonitor is None:
190
            return True
191
        #no need to monitor if manually disabled
192
        if self.is_manually_disabled():
193
            return False
194
        if self.is_disabled():
195
            #keep monitoring if it was us that disabled the miner
196
            #need to keep monitoring (at longer interval) so we can detect when comes online
197
            #if its a planned outage then user should manually disable to stop monitoring
198
            #since = (datetime.utcnow() - self.lastmonitor).total_seconds()
199
            #if since > 10 * 60:
200
            return True
201
            #return False
202
        return True
203
204
    def offline_now(self):
205
        self.status = MinerStatus.Offline
206
        self.offlinecount += 1
207
208
    def online_now(self):
209
        self.status = MinerStatus.Online
210
        self.offlinecount = 0
211
212
    def is_send_offline_alert(self):
213
        #todo: make configurable
214
        if self.offlinecount <= 3:
215
            return True
216
        return False
217
218
    def monitored(self, stats, pool=None, info=None, sec=None):
219
        if stats:
220
            self.lastmonitor = datetime.utcnow()
221
            self.status = MinerStatus.Online
222
        if sec is not None:
223
            self.monitorcount += 1
224
            self.monitortime += sec
225
        #todo: process stats and pool
226
        self.setminerinfo(info)
227
        if pool is not None:
228
            self.minerpool = pool
229
        self.minerstats = stats
230
231
    def monitorresponsetime(self):
232
        if self.monitorcount == 0: return 0
233
        return self.monitortime/self.monitorcount
234
235
    def setminerinfo(self, info):
236
        if info is not None:
237
            self.minerinfo = info
238
            if not self.miner_type:
239
                self.miner_type = info.miner_type
240
            if not self.minerid:
241
                self.minerid = info.minerid
242
243
    def updatefrom(self, updatedminer):
244
        if self.minerid != updatedminer.minerid and self.name != updatedminer.name:
245
            return
246
        if self.minerid == updatedminer.minerid and self.name != updatedminer.name:
247
            self.name = updatedminer.name
248
        self.setminerinfo(updatedminer.minerinfo)
249
250
        #self.minerid = updatedminer.minerid
251
        fields = ['lastmonitor', 'status', 'ipaddress', 'port', 'username', 'password', 'clientid']
252
        fields.append('offlinecount')
253
        fields.append('defaultpool')
254
        fields.append('minerpool')
255
        fields.append('minerstats')
256
        fields.append('networkid')
257
        for fld in fields:
258
            val = getattr(updatedminer, fld)
259
            if val:
260
                setattr(self, fld, val)
261
262
class AvailablePool(object):
263
    """A pool available on a miner
264
    pool_type is the miner type (e.g. Antminer S9)
265
    """
266
267
    def __init__(self, pool_type, named_pool=None, url='', user='', password='x', priority=None):
268
        self.pool_type = pool_type
269
        self.named_pool = named_pool
270
        self.url = url
271
        self.user = user
272
        self.password = password
273
        self.priority = priority
274
275
    @property
276
    def key(self):
277
        return '{0}|{1}'.format(self.url, self.user)
278
279
class MinerApiCall(object):
280
    '''info about one call to miner'''
281
    def __init__(self, miner: Miner):
282
        self.miner = miner
283
        self.when = datetime.now()
284
        self.start_time = None
285
        self.stop_time = None
286
287
    def start(self):
288
        self.start_time = time.perf_counter()
289
    def stop(self):
290
        self.stop_time = time.perf_counter()
291
    def elapsed(self):
292
        return self.stop_time - self.start_time
293
294
class Pool(object):
295
    """A configured (Named) Pool.
296
    Does not have to be attached to miner yet
297
    """
298
299
    def __init__(self, pool_type, name, url, user, priority, password='x'):
300
        self.pool_type = pool_type
301
        self.name = name
302
        self.url = url
303
        self.user = user
304
        self.priority = priority
305
        self.password = password
306
307
    def is_same_as(self, available_pool: AvailablePool):
308
        return available_pool.url == self.url and available_pool.user.startswith(self.user)
309
310
class MinerCurrentPool(object):
311
    '''The current pool where a miner is mining'''
312
    def __init__(self, miner, currentpool=None, currentworker=None, allpools=None):
313
        self.miner = miner
314
        self.poolname = '?'
315
        self.currentpool = currentpool
316
        self.currentworker = currentworker
317
        #allpools is a json object
318
        self.allpools = allpools
319
320
    def findpoolnumberforpool(self, url, worker):
321
        jpools = self.allpools["POOLS"]
322
        for pool in jpools:
323
            thisurl = pool["URL"]
324
            thisworker = pool["User"]
325
            if thisurl == url and thisworker.startswith(worker):
326
                return pool["POOL"]
327
        return None
328