Skip to content

Instantly share code, notes, and snippets.

@ageis
Last active September 16, 2019 16:41
Show Gist options
  • Save ageis/23e243dcee51cbcc75d8d4bdfbf7f076 to your computer and use it in GitHub Desktop.
Save ageis/23e243dcee51cbcc75d8d4bdfbf7f076 to your computer and use it in GitHub Desktop.
Prometheus exporter for monitoring statistics of Zcash daemon

zcash-monitor.py

This is a script written in Python intended to run alongside a Zcash node and export statistics for monitoring purposes. It assumes the existence of zcash-cli in the PATH and access to the RPC interface over localhost.

It tracks stuff like: block height, difficulty, number of peers, network solutions per second, errors, mempool size, size of recent blocks, number of transactions within blocks, chaintips, total bytes received and sent, transaction inputs and outputs, and the make-up of transactions in terms of JoinSplits, shielded, unshielded or mixed. These Zcash metrics are refreshed once every 2 minutes.

How it works

Prometheus is a monitoring system and time-series database.

It works by pulling or scraping numerical metrics from an HTTP endpoint (or "exporter"), and then ingesting and keeping track of them over time.

You can then build queries and alerting rules from this data. This code is targeted to users of both Prometheus and Zcash.

An exporter setup as a scrape target may be local or remote. Prometheus is a great backend for a visualization and analytics software such as Grafana.

Testing and requirements

To see it in action, run zcash-monitor.py and navigate to http://127.0.0.1:8334 in your browser.

Ensure that prometheus_client is installed via pip. If you're using Python 2, then the whichcraft module is another requirement.

Running as a service

I'd also recommend running this persistently as a systemd service. For example:

[Unit]
Description=zcash-monitor
After=network.target zcashd.service

[Service]
ExecStart=/usr/bin/python /usr/local/bin/zcash-monitor.py
KillMode=process
User=zcash
Group=zcash
Restart=on-failure

[Install]
WantedBy=multi-user.target
#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
import time
import subprocess
import sys
from prometheus_client import start_http_server, Gauge, Counter
# Create Prometheus metrics to track zcashd stats.
ZCASH_BLOCKS = Gauge('zcash_blocks', 'Block height')
ZCASH_DIFFICULTY = Gauge('zcash_difficulty', 'Difficulty')
ZCASH_PEERS = Gauge('zcash_peers', 'Number of peers')
ZCASH_SOLS = Gauge('zcash_sols', 'Estimated network solutions per second')
ZCASH_ERRORS = Counter('zcash_errors', 'Number of errors detected')
ZCASH_MEMPOOL_BYTES = Gauge('zcash_mempool_bytes', 'Size of mempool in bytes')
ZCASH_MEMPOOL_SIZE = Gauge('zcash_mempool_size', 'Number of unconfirmed transactions in mempool')
ZCASH_LATEST_BLOCK_SIZE = Gauge('zcash_latest_block_size', 'Size of latest block in bytes')
ZCASH_LATEST_BLOCK_TXS = Gauge('zcash_latest_block_txs', 'Number of transactions in latest block')
ZCASH_CHAINFORK_LOCATION = Gauge('zcash_chainfork_location', 'Block height of chain fork')
ZCASH_CHAINFORK_SIZE = Gauge('zcash_chainfork_size', 'Length of chain fork')
ZCASH_TOTAL_BYTES_RECV = Gauge('zcash_total_bytes_recv', 'Total bytes received')
ZCASH_TOTAL_BYTES_SENT = Gauge('zcash_total_bytes_sent', 'Total bytes sent')
ZCASH_LATEST_BLOCK_INPUTS = Gauge('zcash_latest_block_inputs', 'Number of inputs in transactions of latest block')
ZCASH_LATEST_BLOCK_OUTPUTS = Gauge('zcash_latest_block_outputs', 'Number of outputs in transactions of latest block')
ZCASH_LATEST_BLOCK_JOINSPLITS = Gauge('zcash_latest_block_joinsplits', 'Number of joinsplits in transactions of latest block')
ZCASH_NUM_TRANSPARENT_TX = Gauge('zcash_num_transparent_tx', 'Number of fully transparent transactions in latest block')
ZCASH_NUM_SHIELDED_TX = Gauge('zcash_num_shielded_tx', 'Number of fully shielded transactions in latest block')
ZCASH_NUM_MIXED_TX = Gauge('zcash_num_mixed_tx', 'Number of mixed transactions in latest block')
def find_zcash_cli():
if sys.version_info[0] < 3:
from whichcraft import which
if sys.version_info[0] >= 3:
from shutil import which
return which('zcash-cli')
ZCASH_CLI_PATH = str(find_zcash_cli())
def zcash(cmd):
zcash = subprocess.Popen([ZCASH_CLI_PATH, cmd], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
output = zcash.communicate()[0]
return json.loads(output.decode('utf-8'))
def zcashcli(cmd):
zcash = subprocess.Popen([ZCASH_CLI_PATH, cmd], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
output = zcash.communicate()[0]
return output.decode('utf-8')
def get_block(block_height):
try:
block = subprocess.check_output([ZCASH_CLI_PATH, 'getblock', block_height])
except Exception as e:
print(e)
print('Error: Can\'t retrieve block number ' + block_height + ' from zcashd.')
return None
return json.loads(block.decode('utf-8'))
def get_raw_tx(txid):
try:
rawtx = subprocess.check_output([ZCASH_CLI_PATH, 'getrawtransaction', txid, '1'])
except Exception as e:
print(e)
print('Error: Can\'t retrieve raw transaction ' + txid + ' from zcashd.')
return None
return json.loads(rawtx.decode('utf-8'))
def main():
# Start up the server to expose the metrics.
start_http_server(9000)
while True:
info = zcash('getinfo')
chaintips = zcash('getchaintips')
mempool = zcash('getmempoolinfo')
nettotals = zcash('getnettotals')
latest_block = get_block(str(info['blocks']))
solutions = int(zcashcli('getnetworkhashps'))
ZCASH_BLOCKS.set(info['blocks'])
ZCASH_PEERS.set(info['connections'])
ZCASH_DIFFICULTY.set(info['difficulty'])
ZCASH_SOLS.set(solutions)
if info['errors']:
ZCASH_ERRORS.inc()
ZCASH_MEMPOOL_BYTES.set(mempool['bytes'])
ZCASH_MEMPOOL_SIZE.set(mempool['size'])
ZCASH_TOTAL_BYTES_RECV.set(nettotals['totalbytesrecv'])
ZCASH_TOTAL_BYTES_SENT.set(nettotals['totalbytessent'])
ZCASH_LATEST_BLOCK_SIZE.set(latest_block['size'])
ZCASH_LATEST_BLOCK_TXS.set(len(latest_block['tx']))
inputs, outputs, joinsplits = 0, 0, 0
fullyshielded, fullytransparent, mixed = 0, 0, 0
# counting transaction inputs and outputs requires txindex=1
# to be enabled, which may also necessitate reindex=1 in zcash.conf
if latest_block is not None:
for tx in latest_block['tx']:
if get_raw_tx(tx) is not None:
rawtx = get_raw_tx(tx)
i = len(rawtx['vin'])
inputs += i
o = len(rawtx['vout'])
outputs += o
j = len(rawtx['vjoinsplit'])
joinsplits += j
if j == 0:
fullytransparent += 1
elif i + o == 0:
fullyshielded += 1
else:
mixed += 1
ZCASH_LATEST_BLOCK_INPUTS.set(inputs)
ZCASH_LATEST_BLOCK_OUTPUTS.set(outputs)
ZCASH_LATEST_BLOCK_JOINSPLITS.set(joinsplits)
ZCASH_NUM_TRANSPARENT_TX.set(fullytransparent)
ZCASH_NUM_SHIELDED_TX.set(fullyshielded)
ZCASH_NUM_MIXED_TX.set(mixed)
for chaintip in chaintips:
if chaintip['height'] > (info['blocks'] - 1000):
if chaintip['branchlen'] >= 2:
ZCASH_CHAINFORK_LOCATION.set(chaintip['height'])
ZCASH_CHAINFORK_SIZE.set(chaintip['branchlen'])
break
time.sleep(120)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment