feat: Implement Monero wallet Prometheus exporter
Introduced a Prometheus exporter for Monero wallets, capable of reporting wallet balance metrics. The implementation includes: - A Python script `balance.py` for fetching and exposing the wallet balance both as a Prometheus metric and a JSON endpoint. - Dependencies required for running the exporter specified in `requirements.txt`. - A basic `.gitignore` to prevent committing unnecessary files. - A `README.md` file detailing usage and setup instructions. This exporter simplifies monitoring Monero wallet balances by integrating with Prometheus, providing users with real-time insights into their wallet's status. It automates the process of starting a `monero-wallet-rpc` process if needed, making it more convenient for users to set up in various environments. By running this in a Prometheus monitored environment, users can track wallet balances over time, set up alerts, and integrate with Grafana for visualization purposes. This feature enhances the operability and observability of Monero wallets within a Prometheus ecosystem, catering to the needs of Monero wallet owners who require continuous monitoring and reporting of their crypto assets.
This commit is contained in:
commit
87705d5f51
4 changed files with 128 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
venv/
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
wallet/
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Monero Balance Exporter
|
||||||
|
|
||||||
|
This is a simple Prometheus exporter for Monero wallets. It uses the `monero-wallet-rpc` to get the balance of a wallet and exports it as a Prometheus metric.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MONERO_WALLET_PATH=/path/to/your/wallet MONERO_WALLET_PASSWORD="YourWalletPassword!" python balance.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start the exporter on port 5000. You can change the port by setting the `PORT` environment variable.
|
||||||
|
|
||||||
|
The script handles starting a `monero-wallet-rpc` process and connecting to it, so ensure that the `MONERO_RPC_PORT` is set if the default of `18083` is already in use, or set `MONERO_SKIP_RPC` to any value to skip starting the `monero-wallet-rpc` process and connect to an existing one.
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
The exporter only exposes one metric:
|
||||||
|
|
||||||
|
- `monero_wallet_balance` - The balance of the wallet in XMR.
|
||||||
|
|
||||||
|
The metrics are exposed on the `/metrics` endpoint.
|
||||||
|
|
||||||
|
## JSON
|
||||||
|
|
||||||
|
The exporter also exposes the balance as a JSON object on the `/balance` endpoint.
|
94
balance.py
Normal file
94
balance.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from flask import Flask, jsonify, Response
|
||||||
|
from prometheus_client import Gauge, generate_latest
|
||||||
|
from monero.wallet import Wallet
|
||||||
|
from monero.backends.jsonrpc import JSONRPCWallet
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
wallet_path = os.environ.get("MONERO_WALLET_PATH", "wallet/readonly")
|
||||||
|
rpc_host = os.environ.get("MONERO_RPC_HOST", "localhost")
|
||||||
|
rpc_port = os.environ.get("MONERO_RPC_PORT", 18083)
|
||||||
|
daemon_address = os.environ.get(
|
||||||
|
"MONERO_DAEMON_ADDRESS", "xmr-node.cakewallet.com:18081"
|
||||||
|
)
|
||||||
|
wallet_password = os.environ.get("MONERO_WALLET_PASSWORD", "")
|
||||||
|
|
||||||
|
listen_port = os.environ.get("PORT", 5000)
|
||||||
|
listen_host = os.environ.get("HOST", "localhost")
|
||||||
|
|
||||||
|
skip_wallet_rpc = os.environ.get("MONERO_SKIP_RPC", False)
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
# Function to start the Monero wallet RPC server
|
||||||
|
def start_rpc_server():
|
||||||
|
rpc_command = [
|
||||||
|
"monero-wallet-rpc",
|
||||||
|
"--wallet-file",
|
||||||
|
wallet_path,
|
||||||
|
"--rpc-bind-port",
|
||||||
|
str(rpc_port),
|
||||||
|
"--daemon-address",
|
||||||
|
daemon_address,
|
||||||
|
"--password",
|
||||||
|
wallet_password,
|
||||||
|
"--disable-rpc-login",
|
||||||
|
]
|
||||||
|
return subprocess.Popen(rpc_command)
|
||||||
|
|
||||||
|
|
||||||
|
# Function to check if the RPC server is up
|
||||||
|
def is_rpc_server_up(host, port):
|
||||||
|
try:
|
||||||
|
wallet = Wallet(JSONRPCWallet(host=host, port=port))
|
||||||
|
wallet.height() # Try to get the current blockchain height, just to check if the RPC server is up
|
||||||
|
return True
|
||||||
|
except ConnectionError:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Unexpected error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not skip_wallet_rpc:
|
||||||
|
# Start the wallet RPC server using the public node
|
||||||
|
wallet_rpc_process = start_rpc_server()
|
||||||
|
|
||||||
|
# Wait for the wallet RPC server to be up
|
||||||
|
while not is_rpc_server_up("localhost", rpc_port):
|
||||||
|
log("Waiting for the wallet RPC server to start...")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
log("Wallet RPC server is up!")
|
||||||
|
|
||||||
|
# Connect to the Monero wallet RPC server
|
||||||
|
wallet = Wallet(JSONRPCWallet(host=rpc_host, port=rpc_port))
|
||||||
|
|
||||||
|
# Prometheus gauge for the balance
|
||||||
|
balance_gauge = Gauge("monero_wallet_balance", "Current balance of the Monero wallet")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/balance")
|
||||||
|
def get_balance():
|
||||||
|
balance = wallet.balance()
|
||||||
|
balance_gauge.set(balance)
|
||||||
|
return jsonify({"balance": balance})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/metrics")
|
||||||
|
def metrics():
|
||||||
|
balance = wallet.balance()
|
||||||
|
balance_gauge.set(balance)
|
||||||
|
return Response(generate_latest(), mimetype="text/plain")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
app.run(host=listen_host, port=listen_port)
|
||||||
|
finally:
|
||||||
|
# Ensure the RPC server is terminated when the application exits
|
||||||
|
wallet_rpc_process.terminate()
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
monero
|
||||||
|
pyyaml
|
||||||
|
prometheus_client
|
||||||
|
flask
|
Loading…
Reference in a new issue