added global stale rate to graphs
[p2pool.git] / p2pool / graphs.py
1 import hashlib
2 import os
3 import tempfile
4
5 from twisted.web import resource
6
7 try:
8     import rrdtool
9 except ImportError:
10     class Resource(resource.Resource):
11         def __init__(self):
12             resource.Resource.__init__(self)
13             
14             self.putChild('', self)
15         
16         def render_GET(self, request):
17             if not request.path.endswith('/'):
18                 request.redirect(request.path + '/')
19                 return ''
20             request.setHeader('Content-Type', 'text/html')
21             return '<html><head><title>P2Pool Graphs</title></head><body><p>Install python-rrdtool!</p></body></html>'
22     
23     class Grapher(object):
24         def __init__(self, *args): pass
25         def get_resource(self): return Resource()
26         def __getattr__(self, name): return lambda *args, **kwargs: None
27 else:
28     class Renderer(resource.Resource):
29         def __init__(self, arg_func):
30             self.arg_func = arg_func
31         
32         def render_GET(self, request):
33             handle, filename = tempfile.mkstemp()
34             os.close(handle)
35             
36             rrdtool.graph(filename, '--imgformat', 'PNG', *self.arg_func())
37             
38             request.setHeader('Content-Type', 'image/png')
39             return open(filename, 'rb').read()
40     
41     class Resource(resource.Resource):
42         def __init__(self, grapher):
43             resource.Resource.__init__(self)
44             self.grapher = grapher
45             
46             self.putChild('', self)
47             
48             self.putChild('poolrate_day', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1d',
49                 'DEF:A=%s.poolrate:poolrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last day)',
50                 'DEF:C=%s.stalerate:stalerate:AVERAGE' % (self.grapher.path,), 'LINE1:C#FF0000:Total stale (last day)',
51                 'DEF:B=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#0000FF:Local (last day)']))
52             self.putChild('poolrate_week', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1w',
53                 'DEF:A=%s.poolrate:poolrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last week)',
54                 'DEF:C=%s.stalerate:stalerate:AVERAGE' % (self.grapher.path,), 'LINE1:C#FF0000:Total stale (last week)',
55                 'DEF:B=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#0000FF:Local (last week)']))
56             self.putChild('poolrate_month', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1m',
57                 'DEF:A=%s.poolrate:poolrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last month)',
58                 'DEF:C=%s.stalerate:stalerate:AVERAGE' % (self.grapher.path,), 'LINE1:C#FF0000:Total stale (last month)',
59                 'DEF:B=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#0000FF:Local (last month)']))
60             
61             def get_lines():
62                 res = []
63                 for i, x in enumerate(os.listdir(os.path.dirname(self.grapher.path))):
64                     x2 = os.path.join(os.path.dirname(self.grapher.path), x)
65                     if not x2.startswith(self.grapher.path + '.') or not x2.endswith('.localminer'):
66                         continue
67                     name = x2[len(self.grapher.path + '.'):-len('.localminer')].decode('hex')
68                     res.extend([
69                         'DEF:%i=%s:localminer:AVERAGE' % (i, x2),
70                         'AREA:%i#%s:%s%s' % (i, hashlib.sha256(name).hexdigest()[:6], name, ':STACK' if i != 0 else ''),
71                     ])
72                 return res
73             
74             self.putChild('localrate_day', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1d'] + get_lines() + [
75                 'DEF:A=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last day)',
76                 'DEF:B=%s.localdeadrate:localdeadrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#FF0000:Dead (last day)']))
77             self.putChild('localrate_week', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1w'] + get_lines() + [
78                 'DEF:A=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last week)',
79                 'DEF:B=%s.localdeadrate:localdeadrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#FF0000:Dead (last week)']))
80             self.putChild('localrate_month', Renderer(lambda: ['--lower-limit', '0', '-M', '-E', '--start', '-1m'] + get_lines() + [
81                 'DEF:A=%s.localrate:localrate:AVERAGE' % (self.grapher.path,), 'LINE1:A#0000FF:Total (last month)',
82                 'DEF:B=%s.localdeadrate:localdeadrate:AVERAGE' % (self.grapher.path,), 'LINE1:B#FF0000:Dead (last month)']))
83         
84         def render_GET(self, request):
85             if not request.path.endswith('/'):
86                 request.redirect(request.path + '/')
87                 return ''
88             request.setHeader('Content-Type', 'text/html')
89             return '''<html><head><title>P2Pool Graphs</title></head><body><h1>P2Pool Graphs</h1>
90                 <h2>Pool hash rate:</h2>
91                 <p><img style="display:inline" src="poolrate_day"/> <img style="display:inline" src="poolrate_week"/> <img style="display:inline" src="poolrate_month"/></p>
92                 <h2>Local hash rate:</h2>
93                 <p><img style="display:inline" src="localrate_day"/> <img style="display:inline" src="localrate_week"/> <img style="display:inline" src="localrate_month"/></p>
94             </body></html>'''
95     
96     class Grapher(object):
97         def __init__(self, path):
98             self.path = path
99             
100             if not os.path.exists(self.path + '.poolrate'):
101                 rrdtool.create(self.path + '.poolrate', '--step', '300',
102                     'DS:poolrate:GAUGE:600:U:U',
103                     'RRA:AVERAGE:0.5:1:288', # last day
104                     'RRA:AVERAGE:0.5:7:288', # last week
105                     'RRA:AVERAGE:0.5:30:288', # last month
106                 )
107             if not os.path.exists(self.path + '.stalerate'):
108                 rrdtool.create(self.path + '.stalerate', '--step', '300',
109                     'DS:stalerate:GAUGE:600:U:U',
110                     'RRA:AVERAGE:0.5:1:288', # last day
111                     'RRA:AVERAGE:0.5:7:288', # last week
112                     'RRA:AVERAGE:0.5:30:288', # last month
113                 )
114             if not os.path.exists(self.path + '.localrate'):
115                 rrdtool.create(self.path + '.localrate', '--step', '300',
116                     'DS:localrate:ABSOLUTE:43200:U:U',
117                     'RRA:AVERAGE:0.5:1:288', # last day
118                     'RRA:AVERAGE:0.5:7:288', # last week
119                     'RRA:AVERAGE:0.5:30:288', # last month
120                 )
121             if not os.path.exists(self.path + '.localdeadrate'):
122                 rrdtool.create(self.path + '.localdeadrate', '--step', '300',
123                     'DS:localdeadrate:ABSOLUTE:43200:U:U',
124                     'RRA:AVERAGE:0.5:1:288', # last day
125                     'RRA:AVERAGE:0.5:7:288', # last week
126                     'RRA:AVERAGE:0.5:30:288', # last month
127                 )
128         
129         def add_poolrate_point(self, poolrate, stalerate):
130             rrdtool.update(self.path + '.poolrate', '-t', 'poolrate', 'N:%f' % (poolrate,))
131             rrdtool.update(self.path + '.stalerate', '-t', 'stalerate', 'N:%f' % (stalerate,))
132         
133         def add_localrate_point(self, hashes, dead):
134             rrdtool.update(self.path + '.localrate', '-t', 'localrate', 'N:%f' % (hashes,))
135             rrdtool.update(self.path + '.localdeadrate', '-t', 'localdeadrate', 'N:%f' % (hashes if dead else 0,))
136         
137         def add_localminer_point(self, name, hashes, dead):
138             path = self.path + '.' + name.encode('hex')
139             if not os.path.exists(path + '.localminer'):
140                 rrdtool.create(path + '.localminer', '--step', '300',
141                     'DS:localminer:ABSOLUTE:43200:U:U',
142                     'RRA:AVERAGE:0.5:1:288', # last day
143                     'RRA:AVERAGE:0.5:7:288', # last week
144                     'RRA:AVERAGE:0.5:30:288', # last month
145                 )
146             if not os.path.exists(path + '.localdeadminer'):
147                 rrdtool.create(path + '.localdeadminer', '--step', '300',
148                     'DS:localdeadminer:ABSOLUTE:43200:U:U',
149                     'RRA:AVERAGE:0.5:1:288', # last day
150                     'RRA:AVERAGE:0.5:7:288', # last week
151                     'RRA:AVERAGE:0.5:30:288', # last month
152                 )
153             rrdtool.update(path + '.localminer', '-t', 'localminer', 'N:%f' % (hashes,))
154             rrdtool.update(path + '.localdeadminer', '-t', 'localdeadminer', 'N:%f' % (hashes if dead else 0,))
155         
156         def get_resource(self):
157             return Resource(self)