Projet

Général

Profil

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

root / plugins / other / bitcoind_ @ 31412baa

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

1
#!/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
import time
41
import re
42
import urllib2
43
import json
44

    
45

    
46
DEBUG = False
47

    
48

    
49
def main():
50
    # getinfo variable is read from command name - probably the sym-link name.
51
    request_var = sys.argv[0].split('_', 1)[1] or 'balance'
52
    command = sys.argv[1] if len(sys.argv) > 1 else None
53
    request_labels = {'balance': ('Wallet Balance', 'BTC'),
54
                      'connections': ('Peer Connections', 'Connections'),
55
                      'fees': ("Tip Offered", "BTC"),
56
                      'transactions': ("Transactions", "Transactions",
57
                                       ('confirmed', 'waiting')),
58
                      'block_age': ("Last Block Age", "Seconds"),
59
                      'difficulty': ("Difficulty", ""),
60
                      }
61
    labels = request_labels[request_var]
62
    if len(labels) < 3:
63
        line_labels = [request_var]
64
    else:
65
        line_labels = labels[2]
66

    
67
    if command == 'suggest':
68
        for var_name in request_labels.keys():
69
            print var_name
70
        return
71

    
72
    if command == 'config':
73
        print 'graph_category bitcoin'
74
        print 'graph_title Bitcoin %s' % labels[0]
75
        print 'graph_vlabel %s' % labels[1]
76
        for label in line_labels:
77
            print '%s.label %s' % (label, label)
78
        return
79

    
80
    # Munin should send connection options via environment vars
81
    bitcoin_options = get_env_options('rpcconnect', 'rpcport', 'rpcuser', 'rpcpassword')
82
    bitcoin_options.rpcconnect = bitcoin_options.get('rpcconnect', '127.0.0.1')
83
    bitcoin_options.rpcport = bitcoin_options.get('rpcport', '8332')
84

    
85
    if bitcoin_options.get('rpcuser') is None:
86
        conf_file = os.path.join(os.path.expanduser('~/.bitcoin'), 'bitcoin.conf')
87
        bitcoin_options = parse_conf(conf_file)
88

    
89
    bitcoin_options.require('rpcuser', 'rpcpassword')
90

    
91
    bitcoin = ServiceProxy('http://%s:%s' % (bitcoin_options.rpcconnect,
92
                                             bitcoin_options.rpcport),
93
                           username=bitcoin_options.rpcuser,
94
                           password=bitcoin_options.rpcpassword)
95

    
96
    (info, error) = bitcoin.getinfo()
97

    
98
    if error:
99
        if command == 'autoconf':
100
            print 'no'
101
            return
102
        else:
103
            # TODO: Better way to report errors to Munin-node.
104
            raise ValueError("Could not connect to Bitcoin server.")
105

    
106
    if request_var in ('transactions', 'block_age'):
107
        (info, error) = bitcoin.getblockhash(info['blocks'])
108
        (info, error) = bitcoin.getblock(info)
109
        info['block_age'] = int(time.time()) - info['time']
110
        info['confirmed'] = len(info['tx'])
111

    
112
    if request_var in ('fees', 'transactions'):
113
        (memory_pool, error) = bitcoin.getrawmempool()
114
        if memory_pool:
115
            info['waiting'] = len(memory_pool)
116

    
117
    if command == 'autoconf':
118
        print 'yes'
119
        return
120

    
121
    for label in line_labels:
122
        print "%s.value %s" % (label, info[label])
123

    
124

    
125
def parse_conf(filename):
126
    """ Bitcoin config file parser. """
127

    
128
    options = Options()
129

    
130
    re_line = re.compile(r'^\s*([^#]*)\s*(#.*)?$')
131
    re_setting = re.compile(r'^(.*)\s*=\s*(.*)$')
132
    try:
133
        with open(filename) as file:
134
            for line in file.readlines():
135
                line = re_line.match(line).group(1).strip()
136
                m = re_setting.match(line)
137
                if m is None:
138
                    continue
139
                (var, value) = (m.group(1), m.group(2).strip())
140
                options[var] = value
141
    except:
142
        pass
143

    
144
    return options
145

    
146

    
147
def get_env_options(*vars):
148
    options = Options()
149
    for var in vars:
150
        options[var] = os.getenv(var)
151
    return options
152

    
153

    
154
class Options(dict):
155
    """A dict that allows for object-like property access syntax."""
156
    def __getattr__(self, name):
157
        try:
158
            return self[name]
159
        except KeyError:
160
            raise AttributeError(name)
161

    
162
    def require(self, *names):
163
        missing = []
164
        for name in names:
165
            if self.get(name) is None:
166
                missing.append(name)
167
        if len(missing) > 0:
168
            raise ValueError("Missing required setting%s: %s." %
169
                             ('s' if len(missing) > 1 else '',
170
                              ', '.join(missing)))
171

    
172

    
173
class ServiceProxy(object):
174
    """
175
    Proxy for a JSON-RPC web service. Calls to a function attribute
176
    generates a JSON-RPC call to the host service. If a callback
177
    keyword arg is included, the call is processed as an asynchronous
178
    request.
179

    
180
    Each call returns (result, error) tuple.
181
    """
182
    def __init__(self, url, username=None, password=None):
183
        self.url = url
184
        self.id = 0
185
        self.username = username
186
        self.password = password
187

    
188
    def __getattr__(self, method):
189
        self.id += 1
190
        return Proxy(self, method, id=self.id)
191

    
192

    
193
class Proxy(object):
194
    def __init__(self, service, method, id=None):
195
        self.service = service
196
        self.method = method
197
        self.id = id
198

    
199
    def __call__(self, *args):
200
        if DEBUG:
201
            arg_strings = [json.dumps(arg) for arg in args]
202
            print "Calling %s(%s) @ %s" % (self.method,
203
                                           ', '.join(arg_strings),
204
                                           self.service.url)
205

    
206
        data = {
207
            'method': self.method,
208
            'params': args,
209
            'id': self.id,
210
            }
211
        request = urllib2.Request(self.service.url, json.dumps(data))
212
        if self.service.username:
213
            # Strip the newline from the b64 encoding!
214
            b64 = ('%s:%s' % (self.service.username, self.service.password)).encode('base64')[:-1]
215
            request.add_header('Authorization', 'Basic %s' % b64)
216

    
217
        try:
218
            body = urllib2.urlopen(request).read()
219
        except Exception, e:
220
            return (None, e)
221

    
222
        if DEBUG:
223
            print 'RPC Response (%s): %s' % (self.method, json.dumps(body, indent=4))
224

    
225
        try:
226
            data = json.loads(body)
227
        except ValueError, e:
228
            return (None, e.message)
229
        # TODO: Check that id matches?
230
        return (data['result'], data['error'])
231

    
232

    
233
def get_json_url(url):
234
    request = urllib2.Request(url)
235
    body = urllib2.urlopen(request).read()
236
    data = json.loads(body)
237
    return data
238

    
239

    
240
if __name__ == "__main__":
241
    main()