Projet

Général

Profil

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

root / plugins / currency / bitcoin / bitcoind_ @ 675f1f69

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

1
#!/usr/bin/env python3
2

    
3
"""=cut
4
=head1 NAME
5

    
6
  bitcoind_ - Track Bitcoin Server Variables
7

    
8
=head1 CONFIGURATION
9

    
10
You need to be able to authenticate to the bitcoind server to issue rpc's.
11
This plugin supports two ways to do that:
12

    
13
1) In /etc/munin/plugin-conf.d/bitcoin.conf place:
14

    
15
     [bitcoind_*]
16
     user your-username
17

    
18
   Then be sure your $HOME/.bitcoin/bitcoin.conf has the correct authentication info:
19
       rpcconnect, rpcport, rpcuser, rpcpassword
20

    
21
2) Place your bitcoind authentication directly in /etc/munin/plugin-conf.d/bitcoin.conf
22

    
23
     [bitcoind_*]
24
     env.rpcport 8332
25
     env.rpcconnect 127.0.0.1
26
     env.rpcuser your-username-here
27
     env.rpcpassword your-password-here
28

    
29
To install all available graphs:
30

    
31
    sudo munin-node-configure --libdir=. --suggest --shell | sudo bash
32

    
33
Leave out the "| bash" to get a list of commands you can select from to install
34
individual graphs.
35

    
36
=head1 MAGIC MARKERS
37

    
38
  #%# family=auto
39
  #%# capabilities=autoconf suggest
40

    
41
=head1 LICENSE
42

    
43
MIT License
44

    
45
=head1 AUTHOR
46

    
47
Copyright (C) 2012 Mike Koss
48

    
49
=cut"""
50

    
51
import json
52
import os
53
import re
54
import sys
55
import time
56
import urllib.error
57
import urllib.request
58

    
59

    
60
DEBUG = os.getenv('MUNIN_DEBUG') == '1'
61

    
62

    
63
def main():
64
    # getinfo variable is read from command name - probably the sym-link name.
65
    request_var = sys.argv[0].split('_', 1)[1] or 'balance'
66
    command = sys.argv[1] if len(sys.argv) > 1 else None
67
    request_labels = {'balance': ('Wallet Balance', 'BTC'),
68
                      'connections': ('Peer Connections', 'Connections'),
69
                      'fees': ("Tip Offered", "BTC"),
70
                      'transactions': ("Transactions", "Transactions",
71
                                       ('confirmed', 'waiting')),
72
                      'block_age': ("Last Block Age", "Seconds"),
73
                      'difficulty': ("Difficulty", ""),
74
                      }
75
    labels = request_labels[request_var]
76
    if len(labels) < 3:
77
        line_labels = [request_var]
78
    else:
79
        line_labels = labels[2]
80

    
81
    if command == 'suggest':
82
        for var_name in request_labels.keys():
83
            print(var_name)
84
        return
85

    
86
    if command == 'config':
87
        print('graph_category htc')
88
        print('graph_title Bitcoin %s' % labels[0])
89
        print('graph_vlabel %s' % labels[1])
90
        for label in line_labels:
91
            print('%s.label %s' % (label, label))
92
        return
93

    
94
    # Munin should send connection options via environment vars
95
    bitcoin_options = get_env_options('rpcconnect', 'rpcport', 'rpcuser', 'rpcpassword')
96
    bitcoin_options.rpcconnect = bitcoin_options.get('rpcconnect', '127.0.0.1')
97
    bitcoin_options.rpcport = bitcoin_options.get('rpcport', '8332')
98

    
99
    if bitcoin_options.get('rpcuser') is None:
100
        conf_file = os.path.join(os.path.expanduser('~/.bitcoin'), 'bitcoin.conf')
101
        bitcoin_options = parse_conf(conf_file)
102

    
103
    bitcoin_options.require('rpcuser', 'rpcpassword')
104

    
105
    bitcoin = ServiceProxy('http://%s:%s' % (bitcoin_options.rpcconnect,
106
                                             bitcoin_options.rpcport),
107
                           username=bitcoin_options.rpcuser,
108
                           password=bitcoin_options.rpcpassword)
109

    
110
    (info, error) = bitcoin.getinfo()
111

    
112
    if error:
113
        if command == 'autoconf':
114
            print('no')
115
            return
116
        else:
117
            # TODO: Better way to report errors to Munin-node.
118
            raise ValueError("Could not connect to Bitcoin server.")
119

    
120
    if request_var in ('transactions', 'block_age'):
121
        (info, error) = bitcoin.getblockhash(info['blocks'])
122
        (info, error) = bitcoin.getblock(info)
123
        info['block_age'] = int(time.time()) - info['time']
124
        info['confirmed'] = len(info['tx'])
125

    
126
    if request_var in ('fees', 'transactions'):
127
        (memory_pool, error) = bitcoin.getrawmempool()
128
        if memory_pool:
129
            info['waiting'] = len(memory_pool)
130

    
131
    if command == 'autoconf':
132
        print('yes')
133
        return
134

    
135
    for label in line_labels:
136
        print("%s.value %s" % (label, info[label]))
137

    
138

    
139
def parse_conf(filename):
140
    """ Bitcoin config file parser. """
141

    
142
    options = Options()
143

    
144
    re_line = re.compile(r'^\s*([^#]*)\s*(#.*)?$')
145
    re_setting = re.compile(r'^(.*)\s*=\s*(.*)$')
146
    try:
147
        with open(filename) as file:
148
            for line in file.readlines():
149
                line = re_line.match(line).group(1).strip()
150
                m = re_setting.match(line)
151
                if m is None:
152
                    continue
153
                (var, value) = (m.group(1), m.group(2).strip())
154
                options[var] = value
155
    except OSError:
156
        # the config file may be missing
157
        pass
158

    
159
    return options
160

    
161

    
162
def get_env_options(*vars):
163
    options = Options()
164
    for var in vars:
165
        options[var] = os.getenv(var)
166
    return options
167

    
168

    
169
class Options(dict):
170
    """A dict that allows for object-like property access syntax."""
171
    def __getattr__(self, name):
172
        try:
173
            return self[name]
174
        except KeyError:
175
            raise AttributeError(name)
176

    
177
    def require(self, *names):
178
        missing = []
179
        for name in names:
180
            if self.get(name) is None:
181
                missing.append(name)
182
        if len(missing) > 0:
183
            raise ValueError("Missing required setting%s: %s." %
184
                             ('s' if len(missing) > 1 else '',
185
                              ', '.join(missing)))
186

    
187

    
188
class ServiceProxy(object):
189
    """
190
    Proxy for a JSON-RPC web service. Calls to a function attribute
191
    generates a JSON-RPC call to the host service. If a callback
192
    keyword arg is included, the call is processed as an asynchronous
193
    request.
194

    
195
    Each call returns (result, error) tuple.
196
    """
197
    def __init__(self, url, username=None, password=None):
198
        self.url = url
199
        self.id = 0
200
        self.username = username
201
        self.password = password
202

    
203
    def __getattr__(self, method):
204
        self.id += 1
205
        return Proxy(self, method, id=self.id)
206

    
207

    
208
class Proxy(object):
209
    def __init__(self, service, method, id=None):
210
        self.service = service
211
        self.method = method
212
        self.id = id
213

    
214
    def __call__(self, *args):
215
        if DEBUG:
216
            arg_strings = [json.dumps(arg) for arg in args]
217
            print("Calling %s(%s) @ %s" % (self.method,
218
                                           ', '.join(arg_strings),
219
                                           self.service.url))
220

    
221
        data = {
222
            'method': self.method,
223
            'params': args,
224
            'id': self.id,
225
            }
226
        request = urllib.request.Request(self.service.url, json.dumps(data))
227
        if self.service.username:
228
            # Strip the newline from the b64 encoding!
229
            b64 = ('%s:%s' % (self.service.username, self.service.password)).encode('base64')[:-1]
230
            request.add_header('Authorization', 'Basic %s' % b64)
231

    
232
        try:
233
            body = urllib.request.urlopen(request).read()
234
        except urllib.error.URLError as e:
235
            return (None, e)
236

    
237
        if DEBUG:
238
            print('RPC Response (%s): %s' % (self.method, json.dumps(body, indent=4)))
239

    
240
        try:
241
            data = json.loads(body)
242
        except ValueError as e:
243
            return (None, e.message)
244
        # TODO: Check that id matches?
245
        return (data['result'], data['error'])
246

    
247

    
248
def get_json_url(url):
249
    request = urllib.request.Request(url)
250
    body = urllib.request.urlopen(request).read()
251
    data = json.loads(body)
252
    return data
253

    
254

    
255
if __name__ == "__main__":
256
    main()