Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / other / bitcoind_ @ 430d68ff

Historique | Voir | Annoter | Télécharger (7,35 ko)

1 5f66fcc3 Mike Koss
#!/usr/bin/env python
2
# bitcoind_ Munin plugin for Bitcoin Server Variables
3
#
4
# by Mike Koss
5
# Feb 14, 2012, MIT License
6
#
7
# You need to be able to authenticate to the bitcoind server to issue rpc's.
8
# This plugin supporst 2 ways to do that:
9
#
10
# 1) In /etc/munin/plugin-conf.d/bitcoin.conf place:
11
#
12
#      [bitcoind_*]
13
#      user your-username
14
#
15
#    Then be sure your $HOME/.bitcoin/bitcoin.conf has the correct authentication info:
16
#        rpcconnect, rpcport, rpcuser, rpcpassword
17
#
18
# 2) Place your bitcoind authentication directly in /etc/munin/plugin-conf.d/bitcoin.conf
19
#
20
#      [bitcoind_*]
21
#      env.rpcport 8332
22
#      env.rpcconnect 127.0.0.1
23
#      env.rpcuser your-username-here
24
#      env.rpcpassword your-password-here
25
#
26
# To install all available graphs:
27
#
28
#     sudo munin-node-configure --libdir=. --suggest --shell | sudo bash
29
#
30
# Leave out the "| bash" to get a list of commands you can select from to install
31
# individual graphs.
32
#
33
# Munin plugin tags:
34
#
35
#%# family=auto
36
#%# capabilities=autoconf suggest
37
38
import os
39
import sys
40 4db16377 Mike Koss
import time
41 5f66fcc3 Mike Koss
import re
42
import urllib2
43
import json
44
45 4db16377 Mike Koss
46 5f66fcc3 Mike Koss
DEBUG = False
47
48
49 346aa68d Mike Koss
def main():
50
    # getinfo variable is read from command name - probably the sym-link name.
51 4db16377 Mike Koss
    request_var = sys.argv[0].split('_', 1)[1] or 'balance'
52
    command = sys.argv[1] if len(sys.argv) > 1 else None
53 346aa68d Mike Koss
    request_labels = {'balance': ('Wallet Balance', 'BTC'),
54
                      'connections': ('Peer Connections', 'Connections'),
55 4db16377 Mike Koss
                      'fees': ("Tip Offered", "BTC"),
56
                      'transactions': ("Transactions", "Transactions",
57
                                       ('confirmed', 'waiting')),
58
                      'block_age': ("Last Block Age", "Seconds"),
59 346aa68d Mike Koss
                      }
60
    labels = request_labels[request_var]
61 4db16377 Mike Koss
    if len(labels) < 3:
62
        line_labels = [request_var]
63
    else:
64
        line_labels = labels[2]
65 346aa68d Mike Koss
66 4db16377 Mike Koss
    if command == 'suggest':
67 346aa68d Mike Koss
        for var_name in request_labels.keys():
68
            print var_name
69
        return
70
71 4db16377 Mike Koss
    if command == 'config':
72 346aa68d Mike Koss
        print 'graph_category bitcoin'
73
        print 'graph_title Bitcoin %s' % labels[0]
74
        print 'graph_vlabel %s' % labels[1]
75 4db16377 Mike Koss
        for label in line_labels:
76
            print '%s.label %s' % (label, label)
77 346aa68d Mike Koss
        return
78
79
    # Munin should send connection options via environment vars
80
    bitcoin_options = get_env_options('rpcconnect', 'rpcport', 'rpcuser', 'rpcpassword')
81
    bitcoin_options.rpcconnect = bitcoin_options.get('rpcconnect', '127.0.0.1')
82
    bitcoin_options.rpcport = bitcoin_options.get('rpcport', '8332')
83
84
    if bitcoin_options.get('rpcuser') is None:
85
        conf_file = os.path.join(os.path.expanduser('~/.bitcoin'), 'bitcoin.conf')
86
        bitcoin_options = parse_conf(conf_file)
87
88
    bitcoin_options.require('rpcuser', 'rpcpassword')
89
90
    bitcoin = ServiceProxy('http://%s:%s' % (bitcoin_options.rpcconnect,
91
                                             bitcoin_options.rpcport),
92
                           username=bitcoin_options.rpcuser,
93
                           password=bitcoin_options.rpcpassword)
94
95
    (info, error) = bitcoin.getinfo()
96 4db16377 Mike Koss
97 346aa68d Mike Koss
    if error:
98 4db16377 Mike Koss
        if command == 'autoconf':
99 346aa68d Mike Koss
            print 'no'
100
            return
101
        else:
102 4db16377 Mike Koss
            # TODO: Better way to report errors to Munin-node.
103 346aa68d Mike Koss
            raise ValueError("Could not connect to Bitcoin server.")
104
105 4db16377 Mike Koss
    if request_var in ('transactions', 'block_age'):
106
        block_info = get_json_url('http://blockchain.info/block-height/%d?format=json' %
107
                                  info['blocks'])
108
        last_block = block_info['blocks'][0]
109
        info['block_age'] = int(time.time()) - last_block['time']
110
        info['confirmed'] = len(last_block['tx'])
111
112
    if request_var in ('fees', 'transactions'):
113
        (memory_pool, error) = bitcoin.getmemorypool()
114
        if memory_pool:
115
            info['fees'] = float(memory_pool['coinbasevalue']) / 1e8 - 50.0
116
            info['waiting'] = len(memory_pool['transactions'])
117
118
    if command == 'autoconf':
119 346aa68d Mike Koss
        print 'yes'
120
        return
121
122 4db16377 Mike Koss
    for label in line_labels:
123
        print "%s.value %s" % (label, info[label])
124 346aa68d Mike Koss
125
126 5f66fcc3 Mike Koss
def parse_conf(filename):
127
    """ Bitcoin config file parser. """
128
129
    options = Options()
130
131
    re_line = re.compile(r'^\s*([^#]*)\s*(#.*)?$')
132
    re_setting = re.compile(r'^(.*)\s*=\s*(.*)$')
133
    try:
134
        with open(filename) as file:
135
            for line in file.readlines():
136
                line = re_line.match(line).group(1).strip()
137
                m = re_setting.match(line)
138
                if m is None:
139
                    continue
140
                (var, value) = (m.group(1), m.group(2).strip())
141
                options[var] = value
142
    except:
143
        pass
144
145
    return options
146
147
148
def get_env_options(*vars):
149
    options = Options()
150
    for var in vars:
151
        options[var] = os.getenv(var)
152
    return options
153
154
155
class Options(dict):
156
    """A dict that allows for object-like property access syntax."""
157
    def __getattr__(self, name):
158
        try:
159
            return self[name]
160
        except KeyError:
161
            raise AttributeError(name)
162
163
    def require(self, *names):
164
        missing = []
165
        for name in names:
166
            if self.get(name) is None:
167
                missing.append(name)
168
        if len(missing) > 0:
169
            raise ValueError("Missing required setting%s: %s." %
170
                             ('s' if len(missing) > 1 else '',
171
                              ', '.join(missing)))
172
173
174
class ServiceProxy(object):
175
    """
176
    Proxy for a JSON-RPC web service. Calls to a function attribute
177
    generates a JSON-RPC call to the host service. If a callback
178
    keyword arg is included, the call is processed as an asynchronous
179
    request.
180
181
    Each call returns (result, error) tuple.
182
    """
183
    def __init__(self, url, username=None, password=None):
184
        self.url = url
185
        self.id = 0
186
        self.username = username
187
        self.password = password
188
189
    def __getattr__(self, method):
190
        self.id += 1
191
        return Proxy(self, method, id=self.id)
192
193
194
class Proxy(object):
195
    def __init__(self, service, method, id=None):
196
        self.service = service
197
        self.method = method
198
        self.id = id
199
200
    def __call__(self, *args):
201
        if DEBUG:
202
            arg_strings = [json.dumps(arg) for arg in args]
203
            print "Calling %s(%s) @ %s" % (self.method,
204
                                           ', '.join(arg_strings),
205
                                           self.service.url)
206
207
        data = {
208
            'method': self.method,
209
            'params': args,
210
            'id': self.id,
211
            }
212
        request = urllib2.Request(self.service.url, json.dumps(data))
213
        if self.service.username:
214
            # Strip the newline from the b64 encoding!
215
            b64 = ('%s:%s' % (self.service.username, self.service.password)).encode('base64')[:-1]
216
            request.add_header('Authorization', 'Basic %s' % b64)
217
218
        try:
219
            body = urllib2.urlopen(request).read()
220
        except Exception, e:
221
            return (None, e)
222
223
        if DEBUG:
224
            print 'RPC Response (%s): %s' % (self.method, json.dumps(body, indent=4))
225
226
        try:
227
            data = json.loads(body)
228
        except ValueError, e:
229
            return (None, e.message)
230
        # TODO: Check that id matches?
231
        return (data['result'], data['error'])
232
233
234 4db16377 Mike Koss
def get_json_url(url):
235
    request = urllib2.Request(url)
236
    body = urllib2.urlopen(request).read()
237
    data = json.loads(body)
238
    return data
239
240
241 5f66fcc3 Mike Koss
if __name__ == "__main__":
242
    main()