Projet

Général

Profil

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

root / plugins / currency / bitcoin / bitcoind_ @ fd45fe6c

Historique | Voir | Annoter | Télécharger (8,04 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
     env.bitcoin_configfile /home/your-username/.bitcoin/bitcoin.conf
18

    
19
   Then be sure that the file referenced above (typically: $HOME/.bitcoin/bitcoin.conf)
20
   has the correct authentication info:
21
       rpcconnect, rpcport, rpcuser, rpcpassword
22

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

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

    
31
To install all available graphs:
32

    
33
    sudo munin-node-configure --libdir=. --suggest --shell | sudo bash
34

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

    
38
=head1 MAGIC MARKERS
39

    
40
  #%# family=auto
41
  #%# capabilities=autoconf suggest
42

    
43
=head1 LICENSE
44

    
45
MIT License
46

    
47
=head1 AUTHOR
48

    
49
Copyright (C) 2012 Mike Koss
50

    
51
=cut"""
52

    
53
import base64
54
import json
55
import os
56
import re
57
import sys
58
import time
59
import urllib.error
60
import urllib.request
61

    
62

    
63
DEBUG = os.getenv('MUNIN_DEBUG') == '1'
64

    
65

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

    
84
    if command == 'suggest':
85
        for var_name in request_labels.keys():
86
            print(var_name)
87
        return True
88

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

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

    
102
    if bitcoin_options.get('rpcuser') is None:
103
        conf_file = os.getenv("bitcoin_configfile")
104
        if not conf_file:
105
            print("Missing environment settings (rpcuser/rcpassword or bitcoin_configfile)",
106
                  file=sys.stderr)
107
            return False
108
        elif not os.path.exists(conf_file):
109
            print("Configuration file does not exist: {}".format(conf_file), file=sys.stderr)
110
            return False
111
        else:
112
            bitcoin_options = parse_conf(conf_file)
113

    
114
    bitcoin_options.require('rpcuser', 'rpcpassword')
115

    
116
    bitcoin = ServiceProxy('http://%s:%s' % (bitcoin_options.rpcconnect,
117
                                             bitcoin_options.rpcport),
118
                           username=bitcoin_options.rpcuser,
119
                           password=bitcoin_options.rpcpassword)
120

    
121
    (info, error) = bitcoin.getinfo()
122

    
123
    if error:
124
        if command == 'autoconf':
125
            print('no')
126
            return True
127
        else:
128
            # TODO: Better way to report errors to Munin-node.
129
            print("Could not connect to Bitcoin server.", file=sys.stderr)
130
            return False
131

    
132
    if request_var in ('transactions', 'block_age'):
133
        (info, error) = bitcoin.getblockhash(info['blocks'])
134
        (info, error) = bitcoin.getblock(info)
135
        info['block_age'] = int(time.time()) - info['time']
136
        info['confirmed'] = len(info['tx'])
137

    
138
    if request_var in ('fees', 'transactions'):
139
        (memory_pool, error) = bitcoin.getrawmempool()
140
        if memory_pool:
141
            info['waiting'] = len(memory_pool)
142

    
143
    if command == 'autoconf':
144
        print('yes')
145
        return True
146

    
147
    for label in line_labels:
148
        print("%s.value %s" % (label, info[label]))
149

    
150

    
151
def parse_conf(filename):
152
    """ Bitcoin config file parser. """
153

    
154
    options = Options()
155

    
156
    re_line = re.compile(r'^\s*([^#]*)\s*(#.*)?$')
157
    re_setting = re.compile(r'^(.*)\s*=\s*(.*)$')
158
    try:
159
        with open(filename) as file:
160
            for line in file.readlines():
161
                line = re_line.match(line).group(1).strip()
162
                m = re_setting.match(line)
163
                if m is None:
164
                    continue
165
                (var, value) = (m.group(1), m.group(2).strip())
166
                options[var] = value
167
    except OSError:
168
        # the config file may be missing
169
        pass
170

    
171
    return options
172

    
173

    
174
def get_env_options(*vars):
175
    options = Options()
176
    for var in vars:
177
        value = os.getenv(var)
178
        if value is not None:
179
            options[var] = os.getenv(var)
180
    return options
181

    
182

    
183
class Options(dict):
184
    """A dict that allows for object-like property access syntax."""
185
    def __getattr__(self, name):
186
        try:
187
            return self[name]
188
        except KeyError:
189
            raise AttributeError(name)
190

    
191
    def require(self, *names):
192
        missing = []
193
        for name in names:
194
            if self.get(name) is None:
195
                missing.append(name)
196
        if len(missing) > 0:
197
            print("Missing required setting%s: %s." % ('s' if len(missing) > 1 else '',
198
                                                       ', '.join(missing)),
199
                  file=sys.stderr)
200
            return False
201

    
202

    
203
class ServiceProxy:
204
    """
205
    Proxy for a JSON-RPC web service. Calls to a function attribute
206
    generates a JSON-RPC call to the host service. If a callback
207
    keyword arg is included, the call is processed as an asynchronous
208
    request.
209

    
210
    Each call returns (result, error) tuple.
211
    """
212
    def __init__(self, url, username=None, password=None):
213
        self.url = url
214
        self.id = 0
215
        self.username = username
216
        self.password = password
217

    
218
    def __getattr__(self, method):
219
        self.id += 1
220
        return Proxy(self, method, id=self.id)
221

    
222

    
223
class Proxy:
224
    def __init__(self, service, method, id=None):
225
        self.service = service
226
        self.method = method
227
        self.id = id
228

    
229
    def __call__(self, *args):
230
        if DEBUG:
231
            arg_strings = [json.dumps(arg) for arg in args]
232
            print("Calling %s(%s) @ %s" % (self.method,
233
                                           ', '.join(arg_strings),
234
                                           self.service.url))
235

    
236
        data = {
237
            'method': self.method,
238
            'params': args,
239
            'id': self.id,
240
            }
241
        request = urllib.request.Request(self.service.url, json.dumps(data).encode())
242
        if self.service.username:
243
            auth_string = '%s:%s' % (self.service.username, self.service.password)
244
            auth_b64 = base64.urlsafe_b64encode(auth_string.encode()).decode()
245
            request.add_header('Authorization', 'Basic %s' % auth_b64)
246

    
247
        try:
248
            body = urllib.request.urlopen(request).read()
249
        except urllib.error.URLError as e:
250
            return (None, e)
251

    
252
        if DEBUG:
253
            print('RPC Response (%s): %s' % (self.method, json.dumps(body, indent=4)))
254

    
255
        try:
256
            data = json.loads(body)
257
        except ValueError as e:
258
            return (None, e.message)
259
        # TODO: Check that id matches?
260
        return (data['result'], data['error'])
261

    
262

    
263
def get_json_url(url):
264
    request = urllib.request.Request(url)
265
    body = urllib.request.urlopen(request).read()
266
    data = json.loads(body)
267
    return data
268

    
269

    
270
if __name__ == "__main__":
271
    sys.exit(0 if main() else 1)