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:
Kumi 2024-05-25 22:21:05 +02:00
commit 87705d5f51
Signed by: kumi
GPG key ID: ECBCC9082395383F
4 changed files with 128 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
venv/
*.log
*.pyc
__pycache__/
wallet/

25
README.md Normal file
View 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
View 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
View file

@ -0,0 +1,4 @@
monero
pyyaml
prometheus_client
flask