Added support to query pool mining statistics every X minutes.
This commit is contained in:
+152
-1
@@ -12,11 +12,14 @@ import time
|
|||||||
import signal
|
import signal
|
||||||
import platform
|
import platform
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
import tarfile
|
import tarfile
|
||||||
import zipfile
|
import zipfile
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
class MoneroMiner:
|
class MoneroMiner:
|
||||||
def __init__(self, config_file: str = "miner-monero.json"):
|
def __init__(self, config_file: str = "miner-monero.json"):
|
||||||
@@ -24,6 +27,8 @@ class MoneroMiner:
|
|||||||
self.script_dir = os.path.dirname(os.path.abspath(__file__))
|
self.script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
self.config = None
|
self.config = None
|
||||||
self.process: Optional[subprocess.Popen] = None
|
self.process: Optional[subprocess.Popen] = None
|
||||||
|
self.stats_thread = None
|
||||||
|
self.stop_stats = False
|
||||||
|
|
||||||
def detect_os(self) -> str:
|
def detect_os(self) -> str:
|
||||||
"""Detect the operating system"""
|
"""Detect the operating system"""
|
||||||
@@ -487,7 +492,8 @@ class MoneroMiner:
|
|||||||
"extra_args": [],
|
"extra_args": [],
|
||||||
"log_file": "monero-miner.log",
|
"log_file": "monero-miner.log",
|
||||||
"api_port": 0,
|
"api_port": 0,
|
||||||
"donate_level": 1
|
"donate_level": 1,
|
||||||
|
"stats_update_interval": 300
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(self.config_file, 'w') as f:
|
with open(self.config_file, 'w') as f:
|
||||||
@@ -529,6 +535,145 @@ class MoneroMiner:
|
|||||||
if config['mining_mode'] == 'pool' and 'pool_address' not in config:
|
if config['mining_mode'] == 'pool' and 'pool_address' not in config:
|
||||||
raise ValueError("pool_address is required for pool mining")
|
raise ValueError("pool_address is required for pool mining")
|
||||||
|
|
||||||
|
def is_supportxmr_pool(self) -> bool:
|
||||||
|
"""Check if the configured pool is SupportXMR"""
|
||||||
|
if self.config['mining_mode'] != 'pool':
|
||||||
|
return False
|
||||||
|
|
||||||
|
pool_addr = self.config.get('pool_address', '').lower()
|
||||||
|
return 'supportxmr.com' in pool_addr
|
||||||
|
|
||||||
|
def fetch_pool_stats(self) -> Optional[Dict]:
|
||||||
|
"""Fetch mining statistics from SupportXMR API"""
|
||||||
|
try:
|
||||||
|
wallet = self.config['wallet_address']
|
||||||
|
url = f"https://www.supportxmr.com/api/miner/{wallet}/stats"
|
||||||
|
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header('User-Agent', 'Mozilla/5.0')
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as response:
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
return data
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f"\nError fetching pool stats (HTTP {e.code}): {e.reason}")
|
||||||
|
return None
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
print(f"\nError fetching pool stats (Network): {e.reason}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError fetching pool stats: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format_hashrate(self, hash_rate: float) -> str:
|
||||||
|
"""Format hash rate in human-readable format"""
|
||||||
|
if hash_rate >= 1000000:
|
||||||
|
return f"{hash_rate / 1000000:.2f} MH/s"
|
||||||
|
elif hash_rate >= 1000:
|
||||||
|
return f"{hash_rate / 1000:.2f} KH/s"
|
||||||
|
else:
|
||||||
|
return f"{hash_rate:.2f} H/s"
|
||||||
|
|
||||||
|
def format_xmr(self, piconero: int) -> str:
|
||||||
|
"""Convert piconero to XMR"""
|
||||||
|
xmr = piconero / 1000000000000
|
||||||
|
return f"{xmr:.12f} XMR"
|
||||||
|
|
||||||
|
def format_time_ago(self, timestamp: int) -> str:
|
||||||
|
"""Format timestamp as time ago"""
|
||||||
|
now = int(time.time())
|
||||||
|
diff = now - timestamp
|
||||||
|
|
||||||
|
if diff < 60:
|
||||||
|
return f"{diff} seconds ago"
|
||||||
|
elif diff < 3600:
|
||||||
|
return f"{diff // 60} minutes ago"
|
||||||
|
elif diff < 86400:
|
||||||
|
return f"{diff // 3600} hours ago"
|
||||||
|
else:
|
||||||
|
return f"{diff // 86400} days ago"
|
||||||
|
|
||||||
|
def display_pool_stats(self, stats: Dict):
|
||||||
|
"""Display pool statistics in a readable format"""
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print(f"Pool Statistics - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
hash_rate = stats.get('hash', 0)
|
||||||
|
total_hashes = stats.get('totalHashes', 0)
|
||||||
|
valid_shares = stats.get('validShares', 0)
|
||||||
|
invalid_shares = stats.get('invalidShares', 0)
|
||||||
|
amt_paid = stats.get('amtPaid', 0)
|
||||||
|
amt_due = stats.get('amtDue', 0)
|
||||||
|
txn_count = stats.get('txnCount', 0)
|
||||||
|
last_hash = stats.get('lastHash', 0)
|
||||||
|
|
||||||
|
print(f"Current Hashrate: {self.format_hashrate(hash_rate)}")
|
||||||
|
print(f"Total Hashes: {total_hashes:,}")
|
||||||
|
print(f"Valid Shares: {valid_shares:,}")
|
||||||
|
print(f"Invalid Shares: {invalid_shares:,}")
|
||||||
|
|
||||||
|
if valid_shares + invalid_shares > 0:
|
||||||
|
efficiency = (valid_shares / (valid_shares + invalid_shares)) * 100
|
||||||
|
print(f"Share Efficiency: {efficiency:.2f}%")
|
||||||
|
|
||||||
|
print(f"\nAmount Paid: {self.format_xmr(amt_paid)}")
|
||||||
|
print(f"Amount Due: {self.format_xmr(amt_due)}")
|
||||||
|
print(f"Total Earned: {self.format_xmr(amt_paid + amt_due)}")
|
||||||
|
print(f"Payment Count: {txn_count}")
|
||||||
|
|
||||||
|
if last_hash > 0:
|
||||||
|
print(f"Last Share: {self.format_time_ago(last_hash)}")
|
||||||
|
|
||||||
|
print("="*60 + "\n")
|
||||||
|
|
||||||
|
def stats_monitor_thread(self):
|
||||||
|
"""Background thread to periodically fetch and display pool stats"""
|
||||||
|
interval = self.config.get('stats_update_interval', 300)
|
||||||
|
|
||||||
|
# Display stats immediately
|
||||||
|
print("\nFetching initial pool statistics...")
|
||||||
|
stats = self.fetch_pool_stats()
|
||||||
|
if stats:
|
||||||
|
self.display_pool_stats(stats)
|
||||||
|
else:
|
||||||
|
print("Could not fetch initial pool statistics. Will retry...\n")
|
||||||
|
|
||||||
|
# Continue periodic updates
|
||||||
|
while not self.stop_stats:
|
||||||
|
# Wait for the interval, checking stop flag frequently
|
||||||
|
for _ in range(interval):
|
||||||
|
if self.stop_stats:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if self.stop_stats:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Fetch and display stats
|
||||||
|
stats = self.fetch_pool_stats()
|
||||||
|
if stats:
|
||||||
|
self.display_pool_stats(stats)
|
||||||
|
|
||||||
|
def start_stats_monitor(self):
|
||||||
|
"""Start the stats monitoring thread"""
|
||||||
|
if not self.is_supportxmr_pool():
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nPool stats monitoring enabled for SupportXMR")
|
||||||
|
interval = self.config.get('stats_update_interval', 300)
|
||||||
|
print(f"Stats will be updated every {interval} seconds ({interval // 60} minutes)")
|
||||||
|
|
||||||
|
self.stop_stats = False
|
||||||
|
self.stats_thread = threading.Thread(target=self.stats_monitor_thread, daemon=True)
|
||||||
|
self.stats_thread.start()
|
||||||
|
|
||||||
|
def stop_stats_monitor(self):
|
||||||
|
"""Stop the stats monitoring thread"""
|
||||||
|
if self.stats_thread:
|
||||||
|
self.stop_stats = True
|
||||||
|
self.stats_thread.join(timeout=2)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Initialize the miner - check for xmrig and setup config"""
|
"""Initialize the miner - check for xmrig and setup config"""
|
||||||
# Check if xmrig is installed
|
# Check if xmrig is installed
|
||||||
@@ -676,6 +821,9 @@ class MoneroMiner:
|
|||||||
print(f"Command: {' '.join(cmd)}")
|
print(f"Command: {' '.join(cmd)}")
|
||||||
print("="*60 + "\n")
|
print("="*60 + "\n")
|
||||||
|
|
||||||
|
# Start stats monitoring if using SupportXMR
|
||||||
|
self.start_stats_monitor()
|
||||||
|
|
||||||
# Setup logging if specified
|
# Setup logging if specified
|
||||||
log_file = self.config.get('log_file')
|
log_file = self.config.get('log_file')
|
||||||
log_handle = None
|
log_handle = None
|
||||||
@@ -722,9 +870,12 @@ class MoneroMiner:
|
|||||||
finally:
|
finally:
|
||||||
if log_handle:
|
if log_handle:
|
||||||
log_handle.close()
|
log_handle.close()
|
||||||
|
self.stop_stats_monitor()
|
||||||
|
|
||||||
def stop_mining(self):
|
def stop_mining(self):
|
||||||
"""Stop the mining process"""
|
"""Stop the mining process"""
|
||||||
|
self.stop_stats_monitor()
|
||||||
|
|
||||||
if self.process:
|
if self.process:
|
||||||
print("Stopping miner...")
|
print("Stopping miner...")
|
||||||
self.process.terminate()
|
self.process.terminate()
|
||||||
|
|||||||
Reference in New Issue
Block a user