mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-24 18:07:20 +00:00
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.
267 lines
7.9 KiB
Python
Executable file
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()
|