<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.electroncash.de/index.php?action=history&amp;feed=atom&amp;title=Prometheus_files</id>
	<title>Prometheus files - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.electroncash.de/index.php?action=history&amp;feed=atom&amp;title=Prometheus_files"/>
	<link rel="alternate" type="text/html" href="https://wiki.electroncash.de/index.php?title=Prometheus_files&amp;action=history"/>
	<updated>2026-06-08T03:56:50Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://wiki.electroncash.de/index.php?title=Prometheus_files&amp;diff=135&amp;oldid=prev</id>
		<title>Superuser: Created page with &quot;== bitcoin-monitor.py ==   # bitcoind-monitor.py  #  # An exporter for Prometheus and Bitcoin Core.  #  # Copyright 2018 Kevin M. Gallagher  # Copyright 2019,2020 Jeff Stein  #  # Published at https://github.com/jvstein/bitcoin-prometheus-exporter  # Licensed under BSD 3-clause (see LICENSE).  #  # Dependency licenses (retrieved 2020-05-31):  #   prometheus_client: Apache 2.0  #   python-bitcoinlib: LGPLv3  #   riprova: MIT    import json  import logging  import time  im...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.electroncash.de/index.php?title=Prometheus_files&amp;diff=135&amp;oldid=prev"/>
		<updated>2023-02-10T15:49:27Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;== bitcoin-monitor.py ==   # bitcoind-monitor.py  #  # An exporter for Prometheus and Bitcoin Core.  #  # Copyright 2018 Kevin M. Gallagher  # Copyright 2019,2020 Jeff Stein  #  # Published at https://github.com/jvstein/bitcoin-prometheus-exporter  # Licensed under BSD 3-clause (see LICENSE).  #  # Dependency licenses (retrieved 2020-05-31):  #   prometheus_client: Apache 2.0  #   python-bitcoinlib: LGPLv3  #   riprova: MIT    import json  import logging  import time  im...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;== bitcoin-monitor.py ==&lt;br /&gt;
&lt;br /&gt;
 # bitcoind-monitor.py&lt;br /&gt;
 #&lt;br /&gt;
 # An exporter for Prometheus and Bitcoin Core.&lt;br /&gt;
 #&lt;br /&gt;
 # Copyright 2018 Kevin M. Gallagher&lt;br /&gt;
 # Copyright 2019,2020 Jeff Stein&lt;br /&gt;
 #&lt;br /&gt;
 # Published at https://github.com/jvstein/bitcoin-prometheus-exporter&lt;br /&gt;
 # Licensed under BSD 3-clause (see LICENSE).&lt;br /&gt;
 #&lt;br /&gt;
 # Dependency licenses (retrieved 2020-05-31):&lt;br /&gt;
 #   prometheus_client: Apache 2.0&lt;br /&gt;
 #   python-bitcoinlib: LGPLv3&lt;br /&gt;
 #   riprova: MIT&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 import logging&lt;br /&gt;
 import time&lt;br /&gt;
 import os&lt;br /&gt;
 import signal&lt;br /&gt;
 import sys&lt;br /&gt;
 import socket&lt;br /&gt;
 &lt;br /&gt;
 from datetime import datetime&lt;br /&gt;
 from functools import lru_cache&lt;br /&gt;
 from typing import Any&lt;br /&gt;
 from typing import Dict&lt;br /&gt;
 from typing import List&lt;br /&gt;
 from typing import Union&lt;br /&gt;
 from urllib.parse import quote&lt;br /&gt;
 from wsgiref.simple_server import make_server&lt;br /&gt;
 &lt;br /&gt;
 import riprova&lt;br /&gt;
 &lt;br /&gt;
 from bitcoin.rpc import InWarmupError, Proxy, JSONRPCError&lt;br /&gt;
 from prometheus_client import make_wsgi_app, Gauge, Counter, Info&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(&amp;quot;bitcoin-exporter&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # Create Prometheus metrics to track bitcoind stats.&lt;br /&gt;
 BITCOIN_INFO = Info(&amp;#039;bitcoin&amp;#039;, &amp;#039;The chain the daemon runs on&amp;#039;)&lt;br /&gt;
 BITCOIN_BLOCKS = Gauge(&amp;quot;bitcoin_blocks&amp;quot;, &amp;quot;Block height&amp;quot;)&lt;br /&gt;
 BITCOIN_DIFFICULTY = Gauge(&amp;quot;bitcoin_difficulty&amp;quot;, &amp;quot;Difficulty&amp;quot;)&lt;br /&gt;
 BITCOIN_PEERS = Gauge(&amp;quot;bitcoin_peers&amp;quot;, &amp;quot;Number of peers&amp;quot;)&lt;br /&gt;
 BITCOIN_HASHPS_1 = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_hashps_1&amp;quot;, &amp;quot;Estimated network hash rate per second for the last block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_HASHPS = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_hashps&amp;quot;, &amp;quot;Estimated network hash rate per second for the last 120 blocks&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_WARNINGS = Counter(&amp;quot;bitcoin_warnings&amp;quot;, &amp;quot;Number of network or blockchain warnings detected&amp;quot;)&lt;br /&gt;
 BITCOIN_UPTIME = Gauge(&amp;quot;bitcoin_uptime&amp;quot;, &amp;quot;Number of seconds the Bitcoin daemon has been running&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_MEMINFO_USED = Gauge(&amp;quot;bitcoin_meminfo_used&amp;quot;, &amp;quot;Number of bytes used&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMINFO_FREE = Gauge(&amp;quot;bitcoin_meminfo_free&amp;quot;, &amp;quot;Number of bytes available&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMINFO_TOTAL = Gauge(&amp;quot;bitcoin_meminfo_total&amp;quot;, &amp;quot;Number of bytes managed&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMINFO_LOCKED = Gauge(&amp;quot;bitcoin_meminfo_locked&amp;quot;, &amp;quot;Number of bytes locked&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMINFO_CHUNKS_USED = Gauge(&amp;quot;bitcoin_meminfo_chunks_used&amp;quot;, &amp;quot;Number of allocated chunks&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMINFO_CHUNKS_FREE = Gauge(&amp;quot;bitcoin_meminfo_chunks_free&amp;quot;, &amp;quot;Number of unused chunks&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_MEMPOOL_BYTES = Gauge(&amp;quot;bitcoin_mempool_bytes&amp;quot;, &amp;quot;Size of mempool in bytes&amp;quot;)&lt;br /&gt;
 BITCOIN_MEMPOOL_SIZE = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_mempool_size&amp;quot;, &amp;quot;Number of unconfirmed transactions in mempool&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_MEMPOOL_USAGE = Gauge(&amp;quot;bitcoin_mempool_usage&amp;quot;, &amp;quot;Total memory usage for the mempool&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_LATEST_BLOCK_HEIGHT = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_latest_block_height&amp;quot;, &amp;quot;Height or index of latest block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_LATEST_BLOCK_SIZE = Gauge(&amp;quot;bitcoin_latest_block_size&amp;quot;, &amp;quot;Size of latest block in bytes&amp;quot;)&lt;br /&gt;
 BITCOIN_LATEST_BLOCK_TXS = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_latest_block_txs&amp;quot;, &amp;quot;Number of transactions in latest block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_TXCOUNT = Gauge(&amp;quot;bitcoin_txcount&amp;quot;, &amp;quot;Number of TX since the genesis block&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_NUM_CHAINTIPS = Gauge(&amp;quot;bitcoin_num_chaintips&amp;quot;, &amp;quot;Number of known blockchain branches&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_TOTAL_BYTES_RECV = Gauge(&amp;quot;bitcoin_total_bytes_recv&amp;quot;, &amp;quot;Total bytes received&amp;quot;)&lt;br /&gt;
 BITCOIN_TOTAL_BYTES_SENT = Gauge(&amp;quot;bitcoin_total_bytes_sent&amp;quot;, &amp;quot;Total bytes sent&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_LATEST_BLOCK_INPUTS = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_latest_block_inputs&amp;quot;, &amp;quot;Number of inputs in transactions of latest block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_LATEST_BLOCK_OUTPUTS = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_latest_block_outputs&amp;quot;, &amp;quot;Number of outputs in transactions of latest block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_LATEST_BLOCK_VALUE = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_latest_block_value&amp;quot;, &amp;quot;Bitcoin value of all transactions in the latest block&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_BAN_CREATED = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_ban_created&amp;quot;, &amp;quot;Time the ban was created&amp;quot;, labelnames=[&amp;quot;address&amp;quot;, &amp;quot;reason&amp;quot;]&lt;br /&gt;
 )&lt;br /&gt;
 BITCOIN_BANNED_UNTIL = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_banned_until&amp;quot;, &amp;quot;Time the ban expires&amp;quot;, labelnames=[&amp;quot;address&amp;quot;, &amp;quot;reason&amp;quot;]&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_SERVER_VERSION = Gauge(&amp;quot;bitcoin_server_version&amp;quot;, &amp;quot;The server version&amp;quot;)&lt;br /&gt;
 BITCOIN_PROTOCOL_VERSION = Gauge(&amp;quot;bitcoin_protocol_version&amp;quot;, &amp;quot;The protocol version of the server&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_SIZE_ON_DISK = Gauge(&amp;quot;bitcoin_size_on_disk&amp;quot;, &amp;quot;Estimated size of the block and undo files&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_VERIFICATION_PROGRESS = Gauge(&lt;br /&gt;
     &amp;quot;bitcoin_verification_progress&amp;quot;, &amp;quot;Estimate of verification progress [0..1]&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_RPC_ACTIVE = Gauge(&amp;quot;bitcoin_rpc_active&amp;quot;, &amp;quot;Number of RPC calls being processed&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_DSPROOF_COUNT = Gauge(&amp;quot;bitcoin_dsproof_count&amp;quot;, &amp;quot;Number of Double Spend Proofs&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 EXPORTER_ERRORS = Counter(&lt;br /&gt;
     &amp;quot;bitcoin_exporter_errors&amp;quot;, &amp;quot;Number of errors encountered by the exporter&amp;quot;, labelnames=[&amp;quot;type&amp;quot;]&lt;br /&gt;
 )&lt;br /&gt;
 PROCESS_TIME = Counter(&lt;br /&gt;
     &amp;quot;bitcoin_exporter_process_time&amp;quot;, &amp;quot;Time spent processing metrics from bitcoin node&amp;quot;&lt;br /&gt;
 )&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 BITCOIN_RPC_SCHEME = os.environ.get(&amp;quot;BITCOIN_RPC_SCHEME&amp;quot;, &amp;quot;http&amp;quot;)&lt;br /&gt;
 BITCOIN_RPC_HOST = os.environ.get(&amp;quot;BITCOIN_RPC_HOST&amp;quot;, &amp;quot;localhost&amp;quot;)&lt;br /&gt;
 BITCOIN_RPC_PORT = os.environ.get(&amp;quot;BITCOIN_RPC_PORT&amp;quot;, &amp;quot;8332&amp;quot;)&lt;br /&gt;
 BITCOIN_RPC_USER = os.environ.get(&amp;quot;BITCOIN_RPC_USER&amp;quot;)&lt;br /&gt;
 BITCOIN_RPC_PASSWORD = os.environ.get(&amp;quot;BITCOIN_RPC_PASSWORD&amp;quot;)&lt;br /&gt;
 BITCOIN_CONF_PATH = os.environ.get(&amp;quot;BITCOIN_CONF_PATH&amp;quot;)&lt;br /&gt;
 METRICS_ADDR = os.environ.get(&amp;quot;METRICS_ADDR&amp;quot;, &amp;quot;&amp;quot;)  # empty = any address&lt;br /&gt;
 METRICS_PORT = int(os.environ.get(&amp;quot;METRICS_PORT&amp;quot;, &amp;quot;8334&amp;quot;))&lt;br /&gt;
 RETRIES = int(os.environ.get(&amp;quot;RETRIES&amp;quot;, 5))&lt;br /&gt;
 TIMEOUT = int(os.environ.get(&amp;quot;TIMEOUT&amp;quot;, 30))&lt;br /&gt;
 LOG_LEVEL = os.environ.get(&amp;quot;LOG_LEVEL&amp;quot;, &amp;quot;INFO&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 RETRY_EXCEPTIONS = (InWarmupError, ConnectionError, socket.timeout)&lt;br /&gt;
 &lt;br /&gt;
 RpcResult = Union[Dict[str, Any], List[Any], str, int, float, bool, None]&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def on_retry(err: Exception, next_try: float) -&amp;gt; None:&lt;br /&gt;
     err_type = type(err)&lt;br /&gt;
     exception_name = err_type.__module__ + &amp;quot;.&amp;quot; + err_type.__name__&lt;br /&gt;
     EXPORTER_ERRORS.labels(**{&amp;quot;type&amp;quot;: exception_name}).inc()&lt;br /&gt;
     logger.error(&amp;quot;Retry after exception %s: %s&amp;quot;, exception_name, err)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def error_evaluator(e: Exception) -&amp;gt; bool:&lt;br /&gt;
     return isinstance(e, RETRY_EXCEPTIONS)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=1)&lt;br /&gt;
 def rpc_client_factory():&lt;br /&gt;
     # Configuration is done in this order of precedence:&lt;br /&gt;
     #   - Explicit config file.&lt;br /&gt;
     #   - BITCOIN_RPC_USER and BITCOIN_RPC_PASSWORD environment variables.&lt;br /&gt;
     #   - Default bitcoin config file (as handled by Proxy.__init__).&lt;br /&gt;
     use_conf = (&lt;br /&gt;
         (BITCOIN_CONF_PATH is not None)&lt;br /&gt;
         or (BITCOIN_RPC_USER is None)&lt;br /&gt;
         or (BITCOIN_RPC_PASSWORD is None)&lt;br /&gt;
     )&lt;br /&gt;
 &lt;br /&gt;
     if use_conf:&lt;br /&gt;
         logger.info(&amp;quot;Using config file: %s&amp;quot;, BITCOIN_CONF_PATH or &amp;quot;&amp;lt;default&amp;gt;&amp;quot;)&lt;br /&gt;
         return lambda: Proxy(btc_conf_file=BITCOIN_CONF_PATH, timeout=TIMEOUT)&lt;br /&gt;
     else:&lt;br /&gt;
         host = BITCOIN_RPC_HOST&lt;br /&gt;
         host = &amp;quot;{}:{}@{}&amp;quot;.format(quote(BITCOIN_RPC_USER), quote(BITCOIN_RPC_PASSWORD), host)&lt;br /&gt;
         if BITCOIN_RPC_PORT:&lt;br /&gt;
             host = &amp;quot;{}:{}&amp;quot;.format(host, BITCOIN_RPC_PORT)&lt;br /&gt;
         service_url = &amp;quot;{}://{}&amp;quot;.format(BITCOIN_RPC_SCHEME, host)&lt;br /&gt;
         logger.info(&amp;quot;Using environment configuration&amp;quot;)&lt;br /&gt;
         return lambda: Proxy(service_url=service_url, timeout=TIMEOUT)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def rpc_client():&lt;br /&gt;
     return rpc_client_factory()()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @riprova.retry(&lt;br /&gt;
     timeout=TIMEOUT,&lt;br /&gt;
     backoff=riprova.ExponentialBackOff(),&lt;br /&gt;
     on_retry=on_retry,&lt;br /&gt;
     error_evaluator=error_evaluator,&lt;br /&gt;
 )&lt;br /&gt;
 def bitcoinrpc(*args) -&amp;gt; RpcResult:&lt;br /&gt;
     if logger.isEnabledFor(logging.DEBUG):&lt;br /&gt;
         logger.debug(&amp;quot;RPC call: &amp;quot; + &amp;quot; &amp;quot;.join(str(a) for a in args))&lt;br /&gt;
 &lt;br /&gt;
     result = rpc_client().call(*args)&lt;br /&gt;
 &lt;br /&gt;
     logger.debug(&amp;quot;Result:   %s&amp;quot;, result)&lt;br /&gt;
     return result&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 @lru_cache(maxsize=1)&lt;br /&gt;
 def getblockstats(block_hash: str):&lt;br /&gt;
     try:&lt;br /&gt;
         block = bitcoinrpc(&amp;quot;getblockstats&amp;quot;, block_hash, [&amp;quot;total_size&amp;quot;, &amp;quot;txs&amp;quot;, &amp;quot;height&amp;quot;, &amp;quot;ins&amp;quot;, &amp;quot;outs&amp;quot;, &amp;quot;total_out&amp;quot;])&lt;br /&gt;
     except Exception:&lt;br /&gt;
         logger.exception(&amp;quot;Failed to retrieve block &amp;quot; + block_hash + &amp;quot; statistics from bitcoind.&amp;quot;)&lt;br /&gt;
         return None&lt;br /&gt;
     return block&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def refresh_metrics() -&amp;gt; None:&lt;br /&gt;
     uptime = int(bitcoinrpc(&amp;quot;uptime&amp;quot;))&lt;br /&gt;
     meminfo = bitcoinrpc(&amp;quot;getmemoryinfo&amp;quot;, &amp;quot;stats&amp;quot;)[&amp;quot;locked&amp;quot;]&lt;br /&gt;
     blockchaininfo = bitcoinrpc(&amp;quot;getblockchaininfo&amp;quot;)&lt;br /&gt;
     networkinfo = bitcoinrpc(&amp;quot;getnetworkinfo&amp;quot;)&lt;br /&gt;
     chaintips = len(bitcoinrpc(&amp;quot;getchaintips&amp;quot;))&lt;br /&gt;
     mempool = bitcoinrpc(&amp;quot;getmempoolinfo&amp;quot;)&lt;br /&gt;
     nettotals = bitcoinrpc(&amp;quot;getnettotals&amp;quot;)&lt;br /&gt;
     rpcinfo = bitcoinrpc(&amp;quot;getrpcinfo&amp;quot;)&lt;br /&gt;
     dsprooflist = bitcoinrpc(&amp;quot;getdsprooflist&amp;quot;)&lt;br /&gt;
     txstats = bitcoinrpc(&amp;quot;getchaintxstats&amp;quot;)&lt;br /&gt;
     latest_blockstats = getblockstats(str(blockchaininfo[&amp;quot;bestblockhash&amp;quot;]))&lt;br /&gt;
     hashps_120 = float(bitcoinrpc(&amp;quot;getnetworkhashps&amp;quot;, 120))  # 120 is the default&lt;br /&gt;
     hashps_1 = float(bitcoinrpc(&amp;quot;getnetworkhashps&amp;quot;, 1))&lt;br /&gt;
 &lt;br /&gt;
     banned = bitcoinrpc(&amp;quot;listbanned&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_INFO.info({&amp;quot;chain&amp;quot;: blockchaininfo[&amp;quot;chain&amp;quot;], &amp;quot;coin&amp;quot;: &amp;quot;BCH&amp;quot;})&lt;br /&gt;
     BITCOIN_UPTIME.set(uptime)&lt;br /&gt;
     BITCOIN_BLOCKS.set(blockchaininfo[&amp;quot;blocks&amp;quot;])&lt;br /&gt;
     BITCOIN_PEERS.set(networkinfo[&amp;quot;connections&amp;quot;])&lt;br /&gt;
     BITCOIN_DIFFICULTY.set(blockchaininfo[&amp;quot;difficulty&amp;quot;])&lt;br /&gt;
     BITCOIN_HASHPS.set(hashps_120)&lt;br /&gt;
     BITCOIN_HASHPS_1.set(hashps_1)&lt;br /&gt;
     BITCOIN_SERVER_VERSION.set(networkinfo[&amp;quot;version&amp;quot;])&lt;br /&gt;
     BITCOIN_PROTOCOL_VERSION.set(networkinfo[&amp;quot;protocolversion&amp;quot;])&lt;br /&gt;
     BITCOIN_SIZE_ON_DISK.set(blockchaininfo[&amp;quot;size_on_disk&amp;quot;])&lt;br /&gt;
     BITCOIN_VERIFICATION_PROGRESS.set(blockchaininfo[&amp;quot;verificationprogress&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     for ban in banned:&lt;br /&gt;
         BITCOIN_BAN_CREATED.labels(address=ban[&amp;quot;address&amp;quot;], reason=ban.get(&amp;quot;ban_reason&amp;quot;, &amp;quot;manually added&amp;quot;)).set(&lt;br /&gt;
             ban[&amp;quot;ban_created&amp;quot;]&lt;br /&gt;
         )&lt;br /&gt;
         BITCOIN_BANNED_UNTIL.labels(address=ban[&amp;quot;address&amp;quot;], reason=ban.get(&amp;quot;ban_reason&amp;quot;, &amp;quot;manually added&amp;quot;)).set(&lt;br /&gt;
             ban[&amp;quot;banned_until&amp;quot;]&lt;br /&gt;
         )&lt;br /&gt;
 &lt;br /&gt;
     if networkinfo[&amp;quot;warnings&amp;quot;]:&lt;br /&gt;
         BITCOIN_WARNINGS.inc()&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_TXCOUNT.set(txstats[&amp;quot;txcount&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_NUM_CHAINTIPS.set(chaintips)&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_MEMINFO_USED.set(meminfo[&amp;quot;used&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMINFO_FREE.set(meminfo[&amp;quot;free&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMINFO_TOTAL.set(meminfo[&amp;quot;total&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMINFO_LOCKED.set(meminfo[&amp;quot;locked&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMINFO_CHUNKS_USED.set(meminfo[&amp;quot;chunks_used&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMINFO_CHUNKS_FREE.set(meminfo[&amp;quot;chunks_free&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_MEMPOOL_BYTES.set(mempool[&amp;quot;bytes&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMPOOL_SIZE.set(mempool[&amp;quot;size&amp;quot;])&lt;br /&gt;
     BITCOIN_MEMPOOL_USAGE.set(mempool[&amp;quot;usage&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_TOTAL_BYTES_RECV.set(nettotals[&amp;quot;totalbytesrecv&amp;quot;])&lt;br /&gt;
     BITCOIN_TOTAL_BYTES_SENT.set(nettotals[&amp;quot;totalbytessent&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     if latest_blockstats is not None:&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_SIZE.set(latest_blockstats[&amp;quot;total_size&amp;quot;])&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_TXS.set(latest_blockstats[&amp;quot;txs&amp;quot;])&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_HEIGHT.set(latest_blockstats[&amp;quot;height&amp;quot;])&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_INPUTS.set(latest_blockstats[&amp;quot;ins&amp;quot;])&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_OUTPUTS.set(latest_blockstats[&amp;quot;outs&amp;quot;])&lt;br /&gt;
         BITCOIN_LATEST_BLOCK_VALUE.set(latest_blockstats[&amp;quot;total_out&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     # Subtract one because we don&amp;#039;t want to count the &amp;quot;getrpcinfo&amp;quot; call itself&lt;br /&gt;
     BITCOIN_RPC_ACTIVE.set(len(rpcinfo[&amp;quot;active_commands&amp;quot;]) - 1)&lt;br /&gt;
 &lt;br /&gt;
     BITCOIN_DSPROOF_COUNT.set(len(dsprooflist))&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def sigterm_handler(signal, frame) -&amp;gt; None:&lt;br /&gt;
     logger.critical(&amp;quot;Received SIGTERM. Exiting.&amp;quot;)&lt;br /&gt;
     sys.exit(0)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exception_count(e: Exception) -&amp;gt; None:&lt;br /&gt;
     err_type = type(e)&lt;br /&gt;
     exception_name = err_type.__module__ + &amp;quot;.&amp;quot; + err_type.__name__&lt;br /&gt;
     EXPORTER_ERRORS.labels(**{&amp;quot;type&amp;quot;: exception_name}).inc()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     # Set up logging to look similar to bitcoin logs (UTC).&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         format=&amp;quot;%(asctime)s %(levelname)s %(message)s&amp;quot;, datefmt=&amp;quot;%Y-%m-%dT%H:%M:%SZ&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.Formatter.converter = time.gmtime&lt;br /&gt;
     logger.setLevel(LOG_LEVEL)&lt;br /&gt;
 &lt;br /&gt;
     # Handle SIGTERM gracefully.&lt;br /&gt;
     signal.signal(signal.SIGTERM, sigterm_handler)&lt;br /&gt;
 &lt;br /&gt;
     app = make_wsgi_app()&lt;br /&gt;
 &lt;br /&gt;
     last_refresh = None&lt;br /&gt;
 &lt;br /&gt;
     def refresh_app(*args, **kwargs):&lt;br /&gt;
         nonlocal last_refresh&lt;br /&gt;
         process_start = datetime.now()&lt;br /&gt;
 &lt;br /&gt;
         if not last_refresh or (process_start - last_refresh).total_seconds() &amp;gt; 5: # Limit updates to every 5 seconds&lt;br /&gt;
             # Allow riprova.MaxRetriesExceeded and unknown exceptions to crash the process.&lt;br /&gt;
             try:&lt;br /&gt;
                 refresh_metrics()&lt;br /&gt;
             except riprova.exceptions.RetryError as e:&lt;br /&gt;
                 logger.error(&amp;quot;Refresh failed during retry. Cause: &amp;quot; + str(e))&lt;br /&gt;
                 exception_count(e)&lt;br /&gt;
             except JSONRPCError as e:&lt;br /&gt;
                 logger.debug(&amp;quot;Bitcoin RPC error refresh&amp;quot;, exc_info=True)&lt;br /&gt;
                 exception_count(e)&lt;br /&gt;
             except json.decoder.JSONDecodeError as e:&lt;br /&gt;
                 logger.error(&amp;quot;RPC call did not return JSON. Bad credentials? &amp;quot; + str(e))&lt;br /&gt;
                 sys.exit(1)&lt;br /&gt;
 &lt;br /&gt;
             duration = datetime.now() - process_start&lt;br /&gt;
             PROCESS_TIME.inc(duration.total_seconds())&lt;br /&gt;
             logger.info(&amp;quot;Refresh took %s seconds&amp;quot;, duration)&lt;br /&gt;
             last_refresh = process_start&lt;br /&gt;
 &lt;br /&gt;
         return app(*args, **kwargs)&lt;br /&gt;
 &lt;br /&gt;
     httpd = make_server(METRICS_ADDR, METRICS_PORT, refresh_app)&lt;br /&gt;
     httpd.serve_forever()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;br /&gt;
&lt;br /&gt;
== fulcrum-monitor.py ==&lt;br /&gt;
&lt;br /&gt;
 #!/usr/bin/env python3&lt;br /&gt;
 # Copyright (c) 2021 Axel Gembe &amp;lt;derago@gmail.com&amp;gt;&lt;br /&gt;
 # Copyright 2018 Kevin M. Gallagher&lt;br /&gt;
 # Copyright 2019,2020 Jeff Stein&lt;br /&gt;
 #&lt;br /&gt;
 # Based on https://github.com/jvstein/bitcoin-prometheus-exporter&lt;br /&gt;
 # Published at https://github.com/EchterAgo/fulcrum-prometheus-exporter&lt;br /&gt;
 # Licensed under BSD 3-clause (see LICENSE).&lt;br /&gt;
 &lt;br /&gt;
 import json&lt;br /&gt;
 import logging&lt;br /&gt;
 import time&lt;br /&gt;
 import os&lt;br /&gt;
 import signal&lt;br /&gt;
 import sys&lt;br /&gt;
 import urllib&lt;br /&gt;
 &lt;br /&gt;
 from datetime import datetime&lt;br /&gt;
 from wsgiref.simple_server import make_server&lt;br /&gt;
 &lt;br /&gt;
 from prometheus_client import make_wsgi_app, Gauge, Counter, Info&lt;br /&gt;
 &lt;br /&gt;
 logger = logging.getLogger(&amp;quot;fulcrum-exporter&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # Create Prometheus metrics to track Fulcrum stats.&lt;br /&gt;
 BITCOIND_EXTANT_REQUEST_CONTEXTS = Gauge(&amp;quot;fulcrum_bitcoind_extant_request_contexts&amp;quot;, &amp;quot;Extant bitcoind request contexts&amp;quot;)&lt;br /&gt;
 BITCOIND_REQUEST_CONTEXT_TABLE_SIZE = Gauge(&lt;br /&gt;
     &amp;quot;fulcrum_bitcoind_request_context_table_size&amp;quot;, &amp;quot;bitcoind request context table size&amp;quot;)&lt;br /&gt;
 BITCOIND_REQUEST_TIMEOUT_COUNT = Gauge(&amp;quot;fulcrum_bitcoind_request_timeout_count&amp;quot;, &amp;quot;bitcoind request timeout count&amp;quot;)&lt;br /&gt;
 BITCOIND_REQUEST_ZOMBIE_COUNT = Gauge(&amp;quot;fulcrum_bitcoind_request_zombie_count&amp;quot;, &amp;quot;bitcoind request zombie count&amp;quot;)&lt;br /&gt;
 BITCOIND_RPCCLIENT_COUNT = Gauge(&amp;quot;fulcrum_bitcoind_rpcclient_count&amp;quot;, &amp;quot;Number of bitcoind RPC clients in existence&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 CONTROLLER_INFO = Info(&amp;#039;fulcrum_controller&amp;#039;, &amp;#039;The chain and coin the controller runs on&amp;#039;)&lt;br /&gt;
 CONTROLLER_HEADER_COUNT = Gauge(&amp;quot;fulcrum_controller_header_count&amp;quot;, &amp;quot;Number of headers the controller knows about&amp;quot;)&lt;br /&gt;
 CONTROLLER_TX_NUM = Gauge(&amp;quot;fulcrum_controller_tx_num&amp;quot;, &amp;quot;Number of transactions the controller knows about&amp;quot;)&lt;br /&gt;
 CONTROLLER_UTXO_SET_COUNT = Gauge(&amp;quot;fulcrum_controller_utxo_set_count&amp;quot;, &amp;quot;Number of outputs in the UTXO set&amp;quot;)&lt;br /&gt;
 CONTROLLER_UTXO_SET_SIZE = Gauge(&amp;quot;fulcrum_controller_utxo_set_size_mb&amp;quot;, &amp;quot;Size of the UTXO set&amp;quot;)&lt;br /&gt;
 CONTROLLER_ZMQ_NOTIFICATION_COUNT = Gauge(&amp;quot;fulcrum_controller_zmq_notification_count&amp;quot;, &amp;quot;Number of ZMQ notifications received&amp;quot;)&lt;br /&gt;
 CONTROLLER_TASK_COUNT = Gauge(&amp;quot;fulcrum_controller_task_count&amp;quot;, &amp;quot;Number of controller tasks&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 JEMALLOC_STATS_ACTIVE = Gauge(&amp;quot;fulcrum_jemalloc_stats_active&amp;quot;, &amp;quot;Jemalloc active bytes&amp;quot;)&lt;br /&gt;
 JEMALLOC_STATS_ALLOCATED = Gauge(&amp;quot;fulcrum_jemalloc_stats_allocated&amp;quot;, &amp;quot;Jemalloc allocated bytes&amp;quot;)&lt;br /&gt;
 JEMALLOC_STATS_MAPPED = Gauge(&amp;quot;fulcrum_jemalloc_stats_mapped&amp;quot;, &amp;quot;Jemalloc mapped bytes&amp;quot;)&lt;br /&gt;
 JEMALLOC_STATS_METADATA = Gauge(&amp;quot;fulcrum_jemalloc_stats_metadata&amp;quot;, &amp;quot;Jemalloc metadata bytes&amp;quot;)&lt;br /&gt;
 JEMALLOC_STATS_RESIDENT = Gauge(&amp;quot;fulcrum_jemalloc_stats_resident&amp;quot;, &amp;quot;Jemalloc resident bytes&amp;quot;)&lt;br /&gt;
 JEMALLOC_STATS_RETAINED = Gauge(&amp;quot;fulcrum_jemalloc_stats_retained&amp;quot;, &amp;quot;Jemalloc retained bytes&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 MEMORY_USAGE_PHYSICAL_KB = Gauge(&amp;quot;fulcrum_memory_usage_physical_kb&amp;quot;, &amp;quot;Physical memory usage in kilobytes&amp;quot;)&lt;br /&gt;
 MEMORY_USAGE_VIRTUAL_KB = Gauge(&amp;quot;fulcrum_memory_usage_virtual_kb&amp;quot;, &amp;quot;Virtual memory usage in kilobytes&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 JOB_QUEUE_EXTANT_JOBS = Gauge(&amp;quot;fulcrum_job_queue_extant_jobs&amp;quot;, &amp;quot;Number of jobs in the job queue&amp;quot;)&lt;br /&gt;
 JOB_QUEUE_EXTANT_JOBS_MAX_LIFETIME = Gauge(&amp;quot;fulcrum_job_queue_extant_jobs_max_lifetime&amp;quot;,&lt;br /&gt;
                                            &amp;quot;Maximum number of jobs in the job queue over the process&amp;#039;s life time&amp;quot;)&lt;br /&gt;
 JOB_QUEUE_EXTANT_JOBS_LIMIT = Gauge(&amp;quot;fulcrum_job_queue_extant_jobs_limit&amp;quot;, &amp;quot;Limit for number of jobs in the job queue&amp;quot;)&lt;br /&gt;
 JOB_QUEUE_COUNT_LIFETIME = Gauge(&amp;quot;fulcrum_job_queue_count_lifetime&amp;quot;,&lt;br /&gt;
                                  &amp;quot;Number of jobs processed by the job queue over the process&amp;#039;s life time&amp;quot;)&lt;br /&gt;
 JOB_QUEUE_OVERFLOWS_LIFETIME = Gauge(&amp;quot;fulcrum_job_queue_overflows_lifetime&amp;quot;,&lt;br /&gt;
                                      &amp;quot;Number of job queue overflows over the process&amp;#039;s life time&amp;quot;)&lt;br /&gt;
 JOB_QUEUE_THREAD_COUNT_MAX = Gauge(&amp;quot;fulcrum_job_queue_thread_count_max&amp;quot;, &amp;quot;Maximum number of threads for the job queue&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 SERVER_MANAGER_PEER_COUNT = Gauge(&amp;quot;fulcrum_server_manager_peer_count&amp;quot;, &amp;quot;Peer count&amp;quot;, labelnames=[&amp;quot;peertype&amp;quot;])&lt;br /&gt;
 SERVER_MANAGER_SERVER_CLIENT_COUNT = Gauge(&amp;quot;fulcrum_server_manager_server_client_count&amp;quot;,&lt;br /&gt;
                                            &amp;quot;Client count by server type&amp;quot;, labelnames=[&amp;quot;servertype&amp;quot;])&lt;br /&gt;
 SERVER_MANAGER_CLIENT_COUNT = Gauge(&amp;quot;fulcrum_server_manager_client_count&amp;quot;, &amp;quot;Client count&amp;quot;)&lt;br /&gt;
 SERVER_MANAGER_CLIENT_COUNT_MAX_LIFETIME = Gauge(&lt;br /&gt;
     &amp;quot;fulcrum_server_manager_client_count_max_lifetime&amp;quot;, &amp;quot;Max client count over the process&amp;#039;s life time&amp;quot;)&lt;br /&gt;
 SERVER_MANAGER_TOTAL_LIFETIME_CLIENTS = Gauge(&lt;br /&gt;
     &amp;quot;fulcrum_server_manager_total_lifetime_clients&amp;quot;, &amp;quot;Total number of clients over the process&amp;#039;s life time&amp;quot;)&lt;br /&gt;
 SERVER_MANAGER_TRANSACTIONS_SENT_COUNT = Gauge(&lt;br /&gt;
     &amp;quot;fulcrum_server_manager_transactions_sent_count&amp;quot;, &amp;quot;Number of transactions sent using this server&amp;quot;)&lt;br /&gt;
 SERVER_MANAGER_TRANSACTIONS_SENT_SIZE_BYTES = Gauge(&lt;br /&gt;
     &amp;quot;fulcrum_server_manager_transactions_sent_size_bytes&amp;quot;, &amp;quot;Size of the transactions sent using this server in bytes&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 STORAGE_DB_SHARED_BLOCK_CACHE = Gauge(&amp;quot;fulcrum_storage_db_shared_block_cache&amp;quot;,&lt;br /&gt;
                                       &amp;quot;Storage shared block cache&amp;quot;, labelnames=[&amp;quot;type&amp;quot;])&lt;br /&gt;
 STORAGE_DB_SHARED_WRITE_BUFFER_MANAGER = Gauge(&amp;quot;fulcrum_storage_db_shared_write_buffer_manager&amp;quot;,&lt;br /&gt;
                                                &amp;quot;Storage shared write buffer manager&amp;quot;, labelnames=[&amp;quot;type&amp;quot;])&lt;br /&gt;
 STORAGE_DB_STATS_CUR_SIZE_ALL_MEM_TABLES_BYTES = Gauge(&amp;quot;fulcrum_storage_db_stats_cur_size_all_mem_tables_bytes&amp;quot;,&lt;br /&gt;
                                                        &amp;quot;Approximate size of active and unflushed immutable memtables in bytes&amp;quot;, labelnames=[&amp;quot;database&amp;quot;])&lt;br /&gt;
 STORAGE_DB_STATS_ESTIMATE_TABLE_READERS_MEM_BYTES = Gauge(&amp;quot;fulcrum_storage_db_stats_estimate_table_readers_mem_bytes&amp;quot;,&lt;br /&gt;
                                                           &amp;quot;Estimated memory used for reading SST tables, excluding memory used in block cache (e.g., filter and index blocks) in bytes&amp;quot;, labelnames=[&amp;quot;database&amp;quot;])&lt;br /&gt;
 STORAGE_CACHES_LRU_SIZE_BYTES = Gauge(&amp;quot;fulcrum_storage_caches_lru_size_bytes&amp;quot;,&lt;br /&gt;
                                       &amp;quot;LRU Cache size in bytes&amp;quot;, labelnames=[&amp;quot;cachetype&amp;quot;])&lt;br /&gt;
 STORAGE_CACHES_LRU_ENTRY_COUNT = Gauge(&amp;quot;fulcrum_storage_caches_lru_entry_count&amp;quot;,&lt;br /&gt;
                                        &amp;quot;LRU Cache entry count&amp;quot;, labelnames=[&amp;quot;cachetype&amp;quot;])&lt;br /&gt;
 STORAGE_CACHES_LRU_APPROX_HITS = Gauge(&amp;quot;fulcrum_storage_caches_lru_approx_hits&amp;quot;,&lt;br /&gt;
                                        &amp;quot;LRU Cache approximate hits&amp;quot;, labelnames=[&amp;quot;cachetype&amp;quot;])&lt;br /&gt;
 STORAGE_CACHES_LRU_APPROX_MISSES = Gauge(&amp;quot;fulcrum_storage_caches_lru_approx_misses&amp;quot;,&lt;br /&gt;
                                          &amp;quot;LRU Cache approximate misses&amp;quot;, labelnames=[&amp;quot;cachetype&amp;quot;])&lt;br /&gt;
 STORAGE_CACHES_MERKLEHEADERS = Gauge(&amp;quot;fulcrum_storage_caches_merkleheaders&amp;quot;, &amp;quot;Merkleheader cache size&amp;quot;, labelnames=[&amp;quot;type&amp;quot;])&lt;br /&gt;
 STORAGE_MERGE_CALLS = Gauge(&amp;quot;fulcrum_storage_merge_calls&amp;quot;, &amp;quot;Merged storage calls&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 SUBSMGR_ACTIVE_SUBS_COUNT = Gauge(&amp;quot;fulcrum_subsmgr_active_subs_count&amp;quot;, &amp;quot;Number of active client subscriptions&amp;quot;)&lt;br /&gt;
 SUBSMGR_UNIQUE_SCRIPTHASH_SUBS = Gauge(&amp;quot;fulcrum_subsmgr_unique_subs_count&amp;quot;,&lt;br /&gt;
                                        &amp;quot;Number of unique scripthashes subscribed (including zombies)&amp;quot;)&lt;br /&gt;
 SUBSMGR_PENDING_NOTIF_COUNT = Gauge(&amp;quot;fulcrum_subsmgr_pending_notif_count&amp;quot;, &amp;quot;Number of pending notifications&amp;quot;)&lt;br /&gt;
 SUBSMGR_SUBS_BUCKET_COUNT = Gauge(&amp;quot;fulcrum_subsmgr_subs_bucket_count&amp;quot;, &amp;quot;Number of subscription buckets&amp;quot;)&lt;br /&gt;
 SUBSMGR_SUBS_CACHE_HITS = Gauge(&amp;quot;fulcrum_subsmgr_subs_cache_hits&amp;quot;, &amp;quot;Number of subscription cache hits&amp;quot;)&lt;br /&gt;
 SUBSMGR_SUBS_CACHE_MISSES = Gauge(&amp;quot;fulcrum_subsmgr_subs_cache_misses&amp;quot;, &amp;quot;Number of subscription cache misses&amp;quot;)&lt;br /&gt;
 SUBSMGR_SUBS_LOAD_FACTOR = Gauge(&amp;quot;fulcrum_subsmgr_subs_load_factor&amp;quot;, &amp;quot;Subscription load factor&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 EXPORTER_ERRORS = Counter(&amp;quot;fulcrum_exporter_errors&amp;quot;,&lt;br /&gt;
                           &amp;quot;Number of errors encountered by the exporter&amp;quot;, labelnames=[&amp;quot;type&amp;quot;])&lt;br /&gt;
 PROCESS_TIME = Counter(&amp;quot;fulcrum_exporter_process_time&amp;quot;, &amp;quot;Time spent processing metrics from Fulcrum&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 FULCRUM_STATS_URL = os.environ.get(&amp;quot;FULCRUM_STATS_URL&amp;quot;, &amp;quot;http://127.0.0.1:8080/stats&amp;quot;)&lt;br /&gt;
 METRICS_ADDR = os.environ.get(&amp;quot;METRICS_ADDR&amp;quot;, &amp;quot;&amp;quot;)  # empty = any address&lt;br /&gt;
 METRICS_PORT = int(os.environ.get(&amp;quot;METRICS_PORT&amp;quot;, &amp;quot;50039&amp;quot;))&lt;br /&gt;
 RETRIES = int(os.environ.get(&amp;quot;RETRIES&amp;quot;, 5))&lt;br /&gt;
 TIMEOUT = int(os.environ.get(&amp;quot;TIMEOUT&amp;quot;, 30))&lt;br /&gt;
 LOG_LEVEL = os.environ.get(&amp;quot;LOG_LEVEL&amp;quot;, &amp;quot;INFO&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def refresh_metrics() -&amp;gt; None:&lt;br /&gt;
     with urllib.request.urlopen(FULCRUM_STATS_URL) as stats_json:&lt;br /&gt;
         stats = json.load(stats_json)&lt;br /&gt;
 &lt;br /&gt;
     daemon = stats[&amp;quot;Bitcoin Daemon&amp;quot;]&lt;br /&gt;
     BITCOIND_EXTANT_REQUEST_CONTEXTS.set(daemon[&amp;quot;extant request contexts&amp;quot;])&lt;br /&gt;
     BITCOIND_REQUEST_CONTEXT_TABLE_SIZE.set(daemon[&amp;quot;request context table size&amp;quot;])&lt;br /&gt;
     BITCOIND_REQUEST_TIMEOUT_COUNT.set(daemon[&amp;quot;request timeout count&amp;quot;])&lt;br /&gt;
     BITCOIND_REQUEST_ZOMBIE_COUNT.set(daemon[&amp;quot;request zombie count&amp;quot;])&lt;br /&gt;
     BITCOIND_RPCCLIENT_COUNT.set(len(daemon[&amp;quot;rpc clients&amp;quot;]))&lt;br /&gt;
 &lt;br /&gt;
     ctrl = stats[&amp;quot;Controller&amp;quot;]&lt;br /&gt;
     CONTROLLER_INFO.info({&amp;quot;chain&amp;quot;: ctrl[&amp;quot;Chain&amp;quot;], &amp;quot;coin&amp;quot;: ctrl[&amp;quot;Coin&amp;quot;]})&lt;br /&gt;
     CONTROLLER_HEADER_COUNT.set(ctrl[&amp;quot;Header count&amp;quot;])&lt;br /&gt;
     CONTROLLER_TX_NUM.set(ctrl[&amp;quot;TxNum&amp;quot;])&lt;br /&gt;
     CONTROLLER_UTXO_SET_COUNT.set(ctrl[&amp;quot;UTXO set&amp;quot;])&lt;br /&gt;
     CONTROLLER_UTXO_SET_SIZE.set(float(ctrl[&amp;quot;UTXO set bytes&amp;quot;].split()[0]))&lt;br /&gt;
     CONTROLLER_ZMQ_NOTIFICATION_COUNT.set(sum(value[&amp;quot;notifications&amp;quot;]&lt;br /&gt;
                                               for key, value in ctrl[&amp;quot;ZMQ Notifiers (active)&amp;quot;].items()))&lt;br /&gt;
     CONTROLLER_TASK_COUNT.set(len(ctrl[&amp;quot;tasks&amp;quot;]))&lt;br /&gt;
 &lt;br /&gt;
     if &amp;quot;Jemalloc&amp;quot; in stats:&lt;br /&gt;
         jas = stats[&amp;quot;Jemalloc&amp;quot;][&amp;quot;stats&amp;quot;]&lt;br /&gt;
         JEMALLOC_STATS_ACTIVE.set(jas[&amp;quot;active&amp;quot;])&lt;br /&gt;
         JEMALLOC_STATS_ALLOCATED.set(jas[&amp;quot;allocated&amp;quot;])&lt;br /&gt;
         JEMALLOC_STATS_MAPPED.set(jas[&amp;quot;mapped&amp;quot;])&lt;br /&gt;
         JEMALLOC_STATS_METADATA.set(jas[&amp;quot;metadata&amp;quot;])&lt;br /&gt;
         JEMALLOC_STATS_RESIDENT.set(jas[&amp;quot;resident&amp;quot;])&lt;br /&gt;
         JEMALLOC_STATS_RETAINED.set(jas[&amp;quot;retained&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     MEMORY_USAGE_PHYSICAL_KB.set(stats[&amp;quot;Memory Usage&amp;quot;][&amp;quot;physical kB&amp;quot;])&lt;br /&gt;
     MEMORY_USAGE_VIRTUAL_KB.set(stats[&amp;quot;Memory Usage&amp;quot;][&amp;quot;virtual kB&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     jobq = stats[&amp;quot;Misc&amp;quot;][&amp;quot;Job Queue (Thread Pool)&amp;quot;]&lt;br /&gt;
     JOB_QUEUE_EXTANT_JOBS.set(jobq[&amp;quot;extant jobs&amp;quot;])&lt;br /&gt;
     JOB_QUEUE_EXTANT_JOBS_MAX_LIFETIME.set(jobq[&amp;quot;extant jobs (max lifetime)&amp;quot;])&lt;br /&gt;
     JOB_QUEUE_EXTANT_JOBS_LIMIT.set(jobq[&amp;quot;extant limit&amp;quot;])&lt;br /&gt;
     JOB_QUEUE_COUNT_LIFETIME.set(jobq[&amp;quot;job count (lifetime)&amp;quot;])&lt;br /&gt;
     JOB_QUEUE_OVERFLOWS_LIFETIME.set(jobq[&amp;quot;job queue overflows (lifetime)&amp;quot;])&lt;br /&gt;
     JOB_QUEUE_THREAD_COUNT_MAX.set(jobq[&amp;quot;thread count (max)&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     srvm = stats[&amp;quot;Server Manager&amp;quot;]&lt;br /&gt;
 &lt;br /&gt;
     for peertype in [&amp;quot;bad&amp;quot;, &amp;quot;failed&amp;quot;, &amp;quot;peers&amp;quot;, &amp;quot;queued&amp;quot;]:&lt;br /&gt;
         SERVER_MANAGER_PEER_COUNT.labels(peertype).set(len(srvm[&amp;quot;PeerMgr&amp;quot;][peertype]))&lt;br /&gt;
 &lt;br /&gt;
     for servertype in [&amp;quot;AdminSrv&amp;quot;, &amp;quot;SslSrv&amp;quot;, &amp;quot;TcpSrv&amp;quot;, &amp;quot;WsSrv&amp;quot;, &amp;quot;WssSrv&amp;quot;]:&lt;br /&gt;
         servers = [value for key, value in srvm[&amp;quot;Servers&amp;quot;].items() if key.startswith(servertype)]&lt;br /&gt;
         SERVER_MANAGER_SERVER_CLIENT_COUNT.labels(servertype).set(sum(s[&amp;quot;numClients&amp;quot;] for s in servers))&lt;br /&gt;
 &lt;br /&gt;
     SERVER_MANAGER_CLIENT_COUNT.set(srvm[&amp;quot;number of clients&amp;quot;])&lt;br /&gt;
     SERVER_MANAGER_CLIENT_COUNT_MAX_LIFETIME.set(srvm[&amp;quot;number of clients (max lifetime)&amp;quot;])&lt;br /&gt;
     SERVER_MANAGER_TOTAL_LIFETIME_CLIENTS.set(srvm[&amp;quot;number of clients (total lifetime connections)&amp;quot;])&lt;br /&gt;
     SERVER_MANAGER_TRANSACTIONS_SENT_COUNT.set(srvm[&amp;quot;transactions sent&amp;quot;])&lt;br /&gt;
     SERVER_MANAGER_TRANSACTIONS_SENT_SIZE_BYTES.set(srvm[&amp;quot;transactions sent (bytes)&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     stor = stats[&amp;quot;Storage&amp;quot;]&lt;br /&gt;
     STORAGE_DB_SHARED_BLOCK_CACHE.labels(&amp;quot;capacity&amp;quot;).set(stor[&amp;quot;DB Shared Block Cache&amp;quot;][&amp;quot;capacity&amp;quot;])&lt;br /&gt;
     STORAGE_DB_SHARED_BLOCK_CACHE.labels(&amp;quot;usage&amp;quot;).set(stor[&amp;quot;DB Shared Block Cache&amp;quot;][&amp;quot;usage&amp;quot;])&lt;br /&gt;
     STORAGE_DB_SHARED_WRITE_BUFFER_MANAGER.labels(&amp;quot;buffersize&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;DB Shared Write Buffer Manager&amp;quot;][&amp;quot;buffer size&amp;quot;])&lt;br /&gt;
     STORAGE_DB_SHARED_WRITE_BUFFER_MANAGER.labels(&amp;quot;memoryusage&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;DB Shared Write Buffer Manager&amp;quot;][&amp;quot;memory usage&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     for database in [&amp;quot;blkinfo&amp;quot;, &amp;quot;meta&amp;quot;, &amp;quot;scripthash_history&amp;quot;, &amp;quot;scripthash_unspent&amp;quot;, &amp;quot;undo&amp;quot;, &amp;quot;utxoset&amp;quot;]:&lt;br /&gt;
         STORAGE_DB_STATS_CUR_SIZE_ALL_MEM_TABLES_BYTES.labels(database).set(&lt;br /&gt;
             stor[&amp;quot;DB Stats&amp;quot;][database][&amp;quot;rocksdb.cur-size-all-mem-tables&amp;quot;])&lt;br /&gt;
         STORAGE_DB_STATS_ESTIMATE_TABLE_READERS_MEM_BYTES.labels(database).set(&lt;br /&gt;
             stor[&amp;quot;DB Stats&amp;quot;][database][&amp;quot;rocksdb.estimate-table-readers-mem&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     STORAGE_CACHES_LRU_SIZE_BYTES.labels(&amp;quot;blockheight2txhashes&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: Block Height -&amp;gt; TxHashes&amp;quot;][&amp;quot;Size bytes&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_ENTRY_COUNT.labels(&amp;quot;blockheight2txhashes&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: Block Height -&amp;gt; TxHashes&amp;quot;][&amp;quot;nBlocks&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_APPROX_HITS.labels(&amp;quot;blockheight2txhashes&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: Block Height -&amp;gt; TxHashes&amp;quot;][&amp;quot;~hits&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_APPROX_MISSES.labels(&amp;quot;blockheight2txhashes&amp;quot;).set(&lt;br /&gt;
         stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: Block Height -&amp;gt; TxHashes&amp;quot;][&amp;quot;~misses&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     STORAGE_CACHES_LRU_SIZE_BYTES.labels(&amp;quot;txnum2txhash&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: TxNum -&amp;gt; TxHash&amp;quot;][&amp;quot;Size bytes&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_ENTRY_COUNT.labels(&amp;quot;txnum2txhash&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: TxNum -&amp;gt; TxHash&amp;quot;][&amp;quot;nItems&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_APPROX_HITS.labels(&amp;quot;txnum2txhash&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: TxNum -&amp;gt; TxHash&amp;quot;][&amp;quot;~hits&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_LRU_APPROX_MISSES.labels(&amp;quot;txnum2txhash&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;LRU Cache: TxNum -&amp;gt; TxHash&amp;quot;][&amp;quot;~misses&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     STORAGE_CACHES_MERKLEHEADERS.labels(&amp;quot;count&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;merkleHeaders_Size&amp;quot;])&lt;br /&gt;
     STORAGE_CACHES_MERKLEHEADERS.labels(&amp;quot;bytes&amp;quot;).set(stor[&amp;quot;caches&amp;quot;][&amp;quot;merkleHeaders_SizeBytes&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     STORAGE_MERGE_CALLS.set(stor[&amp;quot;merge calls&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
     subm = stats[&amp;quot;SubsMgr&amp;quot;]&lt;br /&gt;
     SUBSMGR_ACTIVE_SUBS_COUNT.set(subm[&amp;quot;Num. active client subscriptions&amp;quot;])&lt;br /&gt;
     SUBSMGR_UNIQUE_SCRIPTHASH_SUBS.set(subm[&amp;quot;Num. unique scripthashes subscribed (including zombies)&amp;quot;])&lt;br /&gt;
     SUBSMGR_PENDING_NOTIF_COUNT.set(len(subm[&amp;quot;pendingNotifications&amp;quot;]))&lt;br /&gt;
     SUBSMGR_SUBS_BUCKET_COUNT.set(subm[&amp;quot;subscriptions bucket count&amp;quot;])&lt;br /&gt;
     SUBSMGR_SUBS_CACHE_HITS.set(subm[&amp;quot;subscriptions cache hits&amp;quot;])&lt;br /&gt;
     SUBSMGR_SUBS_CACHE_MISSES.set(subm[&amp;quot;subscriptions cache misses&amp;quot;])&lt;br /&gt;
     SUBSMGR_SUBS_LOAD_FACTOR.set(subm[&amp;quot;subscriptions load factor&amp;quot;])&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def sigterm_handler(signal, frame) -&amp;gt; None:&lt;br /&gt;
     logger.critical(&amp;quot;Received SIGTERM. Exiting.&amp;quot;)&lt;br /&gt;
     sys.exit(0)&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def exception_count(e: Exception) -&amp;gt; None:&lt;br /&gt;
     err_type = type(e)&lt;br /&gt;
     exception_name = err_type.__module__ + &amp;quot;.&amp;quot; + err_type.__name__&lt;br /&gt;
     EXPORTER_ERRORS.labels(**{&amp;quot;type&amp;quot;: exception_name}).inc()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 def main():&lt;br /&gt;
     # Set up logging to look similar to bitcoin logs (UTC).&lt;br /&gt;
     logging.basicConfig(&lt;br /&gt;
         format=&amp;quot;%(asctime)s %(levelname)s %(message)s&amp;quot;, datefmt=&amp;quot;%Y-%m-%dT%H:%M:%SZ&amp;quot;&lt;br /&gt;
     )&lt;br /&gt;
     logging.Formatter.converter = time.gmtime&lt;br /&gt;
     logger.setLevel(LOG_LEVEL)&lt;br /&gt;
 &lt;br /&gt;
     # Handle SIGTERM gracefully.&lt;br /&gt;
     signal.signal(signal.SIGTERM, sigterm_handler)&lt;br /&gt;
 &lt;br /&gt;
     app = make_wsgi_app()&lt;br /&gt;
 &lt;br /&gt;
     last_refresh = None&lt;br /&gt;
 &lt;br /&gt;
     def refresh_app(*args, **kwargs):&lt;br /&gt;
         nonlocal last_refresh&lt;br /&gt;
         process_start = datetime.now()&lt;br /&gt;
 &lt;br /&gt;
         if not last_refresh or (process_start - last_refresh).total_seconds() &amp;gt; 1: # Limit updates to every 1 seconds&lt;br /&gt;
             try:&lt;br /&gt;
                 refresh_metrics()&lt;br /&gt;
             except Exception as e:&lt;br /&gt;
                 logger.debug(&amp;quot;Refresh failed&amp;quot;, exc_info=True)&lt;br /&gt;
                 exception_count(e)&lt;br /&gt;
 &lt;br /&gt;
             duration = datetime.now() - process_start&lt;br /&gt;
             PROCESS_TIME.inc(duration.total_seconds())&lt;br /&gt;
             logger.info(&amp;quot;Refresh took %s seconds&amp;quot;, duration)&lt;br /&gt;
             last_refresh = process_start&lt;br /&gt;
 &lt;br /&gt;
         return app(*args, **kwargs)&lt;br /&gt;
 &lt;br /&gt;
     httpd = make_server(METRICS_ADDR, METRICS_PORT, refresh_app)&lt;br /&gt;
     httpd.serve_forever()&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
     main()&lt;/div&gt;</summary>
		<author><name>Superuser</name></author>
	</entry>
</feed>