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() |
