tienhiep-api / backend /core /monitoring.py
Cong123779
deploy: update backend production to new Space
d9bfc2d
Raw
History Blame Contribute Delete
5.58 kB
import os
import shutil
import sqlite3
import datetime
from backend.config import Config
from backend.database.db_manager import get_user_db_conn, get_db
def get_cpu_load():
"""Retrieve CPU 1m, 5m, 15m load average on Linux."""
try:
if os.path.exists("/proc/loadavg"):
with open("/proc/loadavg", "r") as f:
load = f.read().strip().split()
return {
"load_1m": float(load[0]),
"load_5m": float(load[1]),
"load_15m": float(load[2])
}
except Exception:
pass
return {"load_1m": 0.0, "load_5m": 0.0, "load_15m": 0.0}
def get_memory_info():
"""Retrieve system RAM and process Resident Set Size (RSS) memory info."""
mem_total = 0
mem_available = 0
process_rss = 0
try:
if os.path.exists("/proc/meminfo"):
with open("/proc/meminfo", "r") as f:
for line in f:
if line.startswith("MemTotal:"):
mem_total = int(line.split()[1]) * 1024 # kB to bytes
elif line.startswith("MemAvailable:"):
mem_available = int(line.split()[1]) * 1024
if os.path.exists("/proc/self/status"):
with open("/proc/self/status", "r") as f:
for line in f:
if line.startswith("VmRSS:"):
process_rss = int(line.split()[1]) * 1024
break
except Exception:
pass
mem_used = mem_total - mem_available
mem_pct = (mem_used / mem_total * 100) if mem_total > 0 else 0.0
return {
"total_bytes": mem_total,
"used_bytes": mem_used,
"available_bytes": mem_available,
"percent": round(mem_pct, 2),
"process_rss_bytes": process_rss
}
def get_disk_info():
"""Retrieve disk space usage of root partition."""
try:
total, used, free = shutil.disk_usage("/")
percent = (used / total * 100) if total > 0 else 0.0
return {
"total_bytes": total,
"used_bytes": used,
"free_bytes": free,
"percent": round(percent, 2)
}
except Exception:
return {
"total_bytes": 0,
"used_bytes": 0,
"free_bytes": 0,
"percent": 0.0
}
def get_database_sizes():
"""Get the sizes of SQLite databases on disk."""
sizes = {}
db_paths = {
"user_db": Config.USER_DB_PATH,
"books_db": Config.DB_PATH,
"books_advanced": os.path.join(Config.ROOT_DIR, "merged_books_advanced.db"),
"books_fast": os.path.join(Config.ROOT_DIR, "merged_books_fast.db"),
"books_vietphrase": os.path.join(Config.ROOT_DIR, "merged_books_vietphrase.db"),
"books_hanviet": os.path.join(Config.ROOT_DIR, "merged_books_hanviet.db"),
}
for key, path in db_paths.items():
if os.path.exists(path):
sizes[key] = os.path.getsize(path)
else:
sizes[key] = 0
return sizes
def count_log_errors():
"""Scan logs/app.log and count the number of [ERROR] or ERROR level log messages."""
log_path = os.path.join(Config.ROOT_DIR, "logs", "app.log")
count = 0
if not os.path.exists(log_path):
return 0
try:
# Read the file. Since it's limited to 10MB, it's safe to scan
with open(log_path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
if "ERROR" in line or "[ERROR]" in line:
count += 1
except Exception:
pass
return count
def get_system_statistics():
"""Query user database to pull statistics like total users, payments, and API requests."""
stats = {
"total_users": 0,
"vip_users": 0,
"total_payments": 0,
"total_api_keys": 0,
"total_api_calls": 0,
"total_books_scraped": 0
}
try:
conn = get_user_db_conn()
stats["total_users"] = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
stats["vip_users"] = conn.execute("SELECT COUNT(*) FROM users WHERE vip_status = 1").fetchone()[0]
# Check if tables exist before querying
tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
if "payments" in tables:
stats["total_payments"] = conn.execute("SELECT COUNT(*) FROM payments WHERE status = 'completed'").fetchone()[0]
if "api_keys" in tables:
stats["total_api_keys"] = conn.execute("SELECT COUNT(*) FROM api_keys").fetchone()[0]
if "api_usage" in tables:
stats["total_api_calls"] = conn.execute("SELECT COUNT(*) FROM api_usage").fetchone()[0]
conn.close()
except Exception:
pass
try:
main_conn = get_db()
stats["total_books_scraped"] = main_conn.execute("SELECT COUNT(*) FROM books").fetchone()[0]
except Exception:
pass
return stats
def collect_all_metrics():
"""Gather all diagnostics into a consolidated system metrics dictionary."""
return {
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"cpu": get_cpu_load(),
"memory": get_memory_info(),
"disk": get_disk_info(),
"databases": get_database_sizes(),
"error_logs_count": count_log_errors(),
"statistics": get_system_statistics()
}