1
0
Fork 0
mirror of https://github.com/munin-monitoring/contrib.git synced 2025-07-24 18:07:20 +00:00
Munin-Contrib/plugins/currency/bitcoin/bitcoind_
Lars Kruse abdeb7ec65 Plugin bitcoind_: introduce explicit configuration file path
The home directory of the currently effective UID cannot be easily
inferred in python via os.path.expanduser. Since there is no other
simple way for reaching this goal, an explicit environemnt setting is
introduced.
2018-08-24 21:52:38 +02:00

267 lines
7.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""=cut
=head1 NAME
bitcoind_ - Track Bitcoin Server Variables
=head1 CONFIGURATION
You need to be able to authenticate to the bitcoind server to issue rpc's.
This plugin supports two ways to do that:
1) In /etc/munin/plugin-conf.d/bitcoin.conf place:
[bitcoind_*]
user your-username
env.bitcoin_configfile /home/your-username/.bitcoin/bitcoin.conf
Then be sure that the file referenced above (typically: $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.
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf suggest
=head1 LICENSE
MIT License
=head1 AUTHOR
Copyright (C) 2012 Mike Koss
=cut"""
import json
import os
import re
import sys
import time
import urllib.error
import urllib.request
DEBUG = os.getenv('MUNIN_DEBUG') == '1'
def main():
# getinfo variable is read from command name - probably the sym-link name.
request_var = sys.argv[0].split('_', 1)[1] or 'balance'
command = sys.argv[1] if len(sys.argv) > 1 else None
request_labels = {'balance': ('Wallet Balance', 'BTC'),
'connections': ('Peer Connections', 'Connections'),
'fees': ("Tip Offered", "BTC"),
'transactions': ("Transactions", "Transactions",
('confirmed', 'waiting')),
'block_age': ("Last Block Age", "Seconds"),
'difficulty': ("Difficulty", ""),
}
labels = request_labels[request_var]
if len(labels) < 3:
line_labels = [request_var]
else:
line_labels = labels[2]
if command == 'suggest':
for var_name in request_labels.keys():
print(var_name)
return
if command == 'config':
print('graph_category htc')
print('graph_title Bitcoin %s' % labels[0])
print('graph_vlabel %s' % labels[1])
for label in line_labels:
print('%s.label %s' % (label, label))
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.getenv("bitcoin_configfile")
if not conf_file:
print("Missing environment settings (rpcuser/rcpassword or bitcoin_configfile)",
file=sys.stderr)
sys.exit(1)
elif not os.path.exists(conf_file):
print("Configuration file does not exist: {}".format(conf_file), file=sys.stderr)
sys.exit(1)
else:
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 command == 'autoconf':
print('no')
return
else:
# TODO: Better way to report errors to Munin-node.
print("Could not connect to Bitcoin server.", file=sys.stderr)
sys.exit(1)
if request_var in ('transactions', 'block_age'):
(info, error) = bitcoin.getblockhash(info['blocks'])
(info, error) = bitcoin.getblock(info)
info['block_age'] = int(time.time()) - info['time']
info['confirmed'] = len(info['tx'])
if request_var in ('fees', 'transactions'):
(memory_pool, error) = bitcoin.getrawmempool()
if memory_pool:
info['waiting'] = len(memory_pool)
if command == 'autoconf':
print('yes')
return
for label in line_labels:
print("%s.value %s" % (label, info[label]))
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 OSError:
# the config file may be missing
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:
print("Missing required setting%s: %s."
% ('s' if len(missing) > 1 else '', ', '.join(missing)), file=sys.stderr)
sys.exit(1)
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 = urllib.request.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 = urllib.request.urlopen(request).read()
except urllib.error.URLError as 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 as e:
return (None, e.message)
# TODO: Check that id matches?
return (data['result'], data['error'])
def get_json_url(url):
request = urllib.request.Request(url)
body = urllib.request.urlopen(request).read()
data = json.loads(body)
return data
if __name__ == "__main__":
main()