You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
4.0 KiB
137 lines
4.0 KiB
import os |
|
import time |
|
import shutil |
|
import platform |
|
from datetime import datetime, timezone |
|
from zoneinfo import ZoneInfo |
|
|
|
from flask import render_template, jsonify |
|
|
|
APP_START_TS = time.time() |
|
|
|
|
|
def _read_meminfo(): |
|
data = {} |
|
try: |
|
with open("/proc/meminfo", "r", encoding="utf-8") as f: |
|
for line in f: |
|
if ":" not in line: |
|
continue |
|
key, val = line.split(":", 1) |
|
data[key.strip()] = val.strip() |
|
except Exception: |
|
pass |
|
return data |
|
|
|
|
|
def _kb_to_mb(kb_value): |
|
try: |
|
return round(int(kb_value) / 1024, 2) |
|
except Exception: |
|
return None |
|
|
|
|
|
def _server_uptime_seconds(): |
|
try: |
|
with open("/proc/uptime", "r", encoding="utf-8") as f: |
|
return int(float(f.read().split()[0])) |
|
except Exception: |
|
return None |
|
|
|
|
|
def _format_duration(seconds): |
|
if seconds is None: |
|
return None |
|
seconds = int(seconds) |
|
days, rem = divmod(seconds, 86400) |
|
hours, rem = divmod(rem, 3600) |
|
minutes, secs = divmod(rem, 60) |
|
return f"{days}d {hours}h {minutes}m {secs}s" |
|
|
|
|
|
def _health_payload(app): |
|
now_utc = datetime.now(timezone.utc) |
|
now_toronto = now_utc.astimezone(ZoneInfo("America/Toronto")) |
|
|
|
load1 = load5 = load15 = None |
|
try: |
|
load1, load5, load15 = os.getloadavg() |
|
except Exception: |
|
pass |
|
|
|
meminfo = _read_meminfo() |
|
mem_total_kb = None |
|
mem_available_kb = None |
|
mem_used_kb = None |
|
mem_used_percent = None |
|
try: |
|
mem_total_kb = int(meminfo.get("MemTotal", "0 kB").split()[0]) |
|
mem_available_kb = int(meminfo.get("MemAvailable", "0 kB").split()[0]) |
|
mem_used_kb = mem_total_kb - mem_available_kb |
|
if mem_total_kb > 0: |
|
mem_used_percent = round((mem_used_kb / mem_total_kb) * 100, 2) |
|
except Exception: |
|
pass |
|
|
|
disk = shutil.disk_usage("/") |
|
|
|
db_ok = False |
|
db_error = None |
|
try: |
|
connector = app.config.get("OTB_HEALTH_DB_CONNECTOR") |
|
if callable(connector): |
|
conn = connector() |
|
cur = conn.cursor() |
|
cur.execute("SELECT 1") |
|
cur.fetchone() |
|
cur.close() |
|
conn.close() |
|
db_ok = True |
|
else: |
|
db_error = "DB connector not registered" |
|
except Exception as e: |
|
db_error = str(e) |
|
|
|
app_uptime = int(time.time() - APP_START_TS) |
|
server_uptime = _server_uptime_seconds() |
|
|
|
payload = { |
|
"status": "ok" if db_ok else "degraded", |
|
"app_name": "otb_billing", |
|
"hostname": platform.node(), |
|
"server_time_utc": now_utc.isoformat(), |
|
"server_time_toronto": now_toronto.isoformat(), |
|
"app_uptime_seconds": app_uptime, |
|
"app_uptime_human": _format_duration(app_uptime), |
|
"server_uptime_seconds": server_uptime, |
|
"server_uptime_human": _format_duration(server_uptime), |
|
"load_average": {"1m": load1, "5m": load5, "15m": load15}, |
|
"memory": { |
|
"total_mb": _kb_to_mb(mem_total_kb) if mem_total_kb is not None else None, |
|
"available_mb": _kb_to_mb(mem_available_kb) if mem_available_kb is not None else None, |
|
"used_mb": _kb_to_mb(mem_used_kb) if mem_used_kb is not None else None, |
|
"used_percent": mem_used_percent, |
|
}, |
|
"disk_root": { |
|
"total_gb": round(disk.total / (1024**3), 2), |
|
"used_gb": round(disk.used / (1024**3), 2), |
|
"free_gb": round(disk.free / (1024**3), 2), |
|
"used_percent": round((disk.used / disk.total) * 100, 2) if disk.total else None, |
|
}, |
|
"database": {"ok": db_ok, "error": db_error}, |
|
} |
|
|
|
http_code = 200 if db_ok else 503 |
|
return payload, http_code |
|
|
|
|
|
def register_health_routes(app): |
|
@app.route("/health", methods=["GET"]) |
|
def health_page(): |
|
health, http_code = _health_payload(app) |
|
return render_template("health.html", health=health), http_code |
|
|
|
@app.route("/health.json", methods=["GET"]) |
|
def health_json(): |
|
health, http_code = _health_payload(app) |
|
return jsonify(health), http_code
|
|
|