#!/usr/bin/env python # bitcoind_ Munin plugin for Bitcoin Server Variables # # by Mike Koss # Feb 14, 2012, MIT License # # You need to be able to authenticate to the bitcoind server to issue rpc's. # This plugin supporst 2 ways to do that: # # 1) In /etc/munin/plugin-conf.d/bitcoin.conf place: # # [bitcoind_*] # user your-username # # Then be sure your $HOME/.bitcoin/bitcoin.conf has the correct authentication info: # rpcconnect, rpcport, rpcuser, rpcpassword # # 2) Place your bitcoind authentication directly in /etc/munin/plugin-conf.d/bitcoin.conf # # [bitcoind_*] # env.rpcport 8332 # env.rpcconnect 127.0.0.1 # env.rpcuser your-username-here # env.rpcpassword your-password-here # # To install all available graphs: # # sudo munin-node-configure --libdir=. --suggest --shell | sudo bash # # Leave out the "| bash" to get a list of commands you can select from to install # individual graphs. # # Munin plugin tags: # #%# family=auto #%# capabilities=autoconf suggest import os import sys import re import urllib2 import json DEBUG = False def parse_conf(filename): """ Bitcoin config file parser. """ options = Options() re_line = re.compile(r'^\s*([^#]*)\s*(#.*)?$') re_setting = re.compile(r'^(.*)\s*=\s*(.*)$') try: with open(filename) as file: for line in file.readlines(): line = re_line.match(line).group(1).strip() m = re_setting.match(line) if m is None: continue (var, value) = (m.group(1), m.group(2).strip()) options[var] = value except: pass return options def get_env_options(*vars): options = Options() for var in vars: options[var] = os.getenv(var) return options class Options(dict): """A dict that allows for object-like property access syntax.""" def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) def require(self, *names): missing = [] for name in names: if self.get(name) is None: missing.append(name) if len(missing) > 0: raise ValueError("Missing required setting%s: %s." % ('s' if len(missing) > 1 else '', ', '.join(missing))) def main(): # Info variable is read from command name - probably the sym-link name. request_var = sys.argv[0].split('_')[1] or 'balance' request_labels = {'balance': ('Wallet Ballance', 'BTC'), 'blocks': ('Block Number', 'Blocks'), 'connections': ('Peer Connections', 'Connections'), 'difficulty': ('Current Difficulty', 'Difficulty'), 'errors': ("Errors", 'Errors',) } labels = request_labels[request_var] if len(sys.argv) > 1 and sys.argv[1] == 'suggest': for var_name in request_labels.keys(): print var_name return if len(sys.argv) > 1 and sys.argv[1] == 'config': print """graph_title Bitcoin %s graph_category bitcoin graph_vlabel %s %s.label %s """ % (labels[0], labels[1], request_var, request_var) return # Munin should send connection options via environment vars bitcoin_options = get_env_options('rpcconnect', 'rpcport', 'rpcuser', 'rpcpassword') bitcoin_options.rpcconnect = bitcoin_options.get('rpcconnect', '127.0.0.1') bitcoin_options.rpcport = bitcoin_options.get('rpcport', '8332') if bitcoin_options.get('rpcuser') is None: conf_file = os.path.join(os.path.expanduser('~/.bitcoin'), 'bitcoin.conf') bitcoin_options = parse_conf(conf_file) bitcoin_options.require('rpcuser', 'rpcpassword') bitcoin = ServiceProxy('http://%s:%s' % (bitcoin_options.rpcconnect, bitcoin_options.rpcport), username=bitcoin_options.rpcuser, password=bitcoin_options.rpcpassword) (info, error) = bitcoin.getinfo() if error: if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': print 'no' return else: raise ValueError("Could not connect to Bitcoin server.") if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': print 'yes' return print "%s.value %s" % (request_var, info[request_var]) class ServiceProxy(object): """ Proxy for a JSON-RPC web service. Calls to a function attribute generates a JSON-RPC call to the host service. If a callback keyword arg is included, the call is processed as an asynchronous request. Each call returns (result, error) tuple. """ def __init__(self, url, username=None, password=None): self.url = url self.id = 0 self.username = username self.password = password def __getattr__(self, method): self.id += 1 return Proxy(self, method, id=self.id) class Proxy(object): def __init__(self, service, method, id=None): self.service = service self.method = method self.id = id def __call__(self, *args): if DEBUG: arg_strings = [json.dumps(arg) for arg in args] print "Calling %s(%s) @ %s" % (self.method, ', '.join(arg_strings), self.service.url) data = { 'method': self.method, 'params': args, 'id': self.id, } request = urllib2.Request(self.service.url, json.dumps(data)) if self.service.username: # Strip the newline from the b64 encoding! b64 = ('%s:%s' % (self.service.username, self.service.password)).encode('base64')[:-1] request.add_header('Authorization', 'Basic %s' % b64) try: body = urllib2.urlopen(request).read() except Exception, e: return (None, e) if DEBUG: print 'RPC Response (%s): %s' % (self.method, json.dumps(body, indent=4)) try: data = json.loads(body) except ValueError, e: return (None, e.message) # TODO: Check that id matches? return (data['result'], data['error']) if __name__ == "__main__": main()