Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions diagnostic/build-fe9317ff-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"timestamp": "2026-07-14T05:37:55.265088", "commit": "fe9317ff", "status": "ok"}
62 changes: 62 additions & 0 deletions diagnostic/build-fe9317ff.logd
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"timestamp": "2026-07-14T05:37:55.180492",
"hostname": "iZ2zeblr7v0sb6ge3pl74yZ",
"services": {
"backend": {
"status": "CRITICAL",
"detail": "[Errno 111] Connection refused",
"code": 0,
"endpoint": "http://localhost:8080/health"
},
"market": {
"status": "CRITICAL",
"detail": "[Errno 111] Connection refused",
"code": 0,
"endpoint": "http://localhost:8081/health"
},
"frailbox": {
"status": "CRITICAL",
"detail": "[Errno 111] Connection refused",
"code": 0,
"endpoint": "http://localhost:8082/health"
},
"frontend": {
"status": "CRITICAL",
"detail": "[Errno 111] Connection refused",
"code": 0,
"endpoint": "http://localhost:3000/"
}
},
"infrastructure": {
"postgresql": {
"status": "CRITICAL",
"detail": "Connection refused",
"endpoint": "localhost:5432"
},
"redis": {
"status": "CRITICAL",
"detail": "Connection refused",
"endpoint": "localhost:6379"
},
"kafka": {
"status": "CRITICAL",
"detail": "Connection refused",
"endpoint": "localhost:9092"
}
},
"system": {
"disk": {
"status": "OK",
"detail": "44.7% used (17GB/39GB)"
},
"memory": {
"status": "OK",
"detail": "71.9% used (1.1GB/1.6GB)"
},
"load": {
"status": "OK",
"detail": "Load: 0.24 (12% of 2 cores)"
}
},
"overall_status": "DEGRADED"
}
196 changes: 148 additions & 48 deletions tools/health_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import argparse
import json
import os
import platform
import socket
import ssl
import subprocess
Expand Down Expand Up @@ -68,6 +69,151 @@
# CHECK FUNCTIONS
# ---------------------------------------------------------------------------


def _read_proc_meminfo() -> Optional[Dict[str, int]]:
"""Read /proc/meminfo on Linux. Returns None if unavailable."""
try:
with open("/proc/meminfo") as f:
meminfo = {}
for line in f:
parts = line.split(":")
if len(parts) == 2:
key = parts[0].strip()
value = parts[1].strip().replace(" kB", "")
try:
meminfo[key] = int(value) * 1024
except ValueError:
pass
return meminfo
except (FileNotFoundError, IOError, OSError):
return None


def _memory_usage_from_proc() -> Optional[Tuple[str, str, float]]:
"""Compute memory usage from /proc/meminfo (Linux only)."""
meminfo = _read_proc_meminfo()
if meminfo is None:
return None
total = meminfo.get("MemTotal", 0)
available = meminfo.get("MemAvailable", 0)
if total == 0:
return None
used = total - available
pct = (used / total) * 100
return _format_memory_result(pct, used, total)


def _memory_usage_from_sysctl() -> Optional[Tuple[str, str, float]]:
"""Compute memory usage via sysctl (macOS/BSD)."""
try:
import struct
# hw.memsize on macOS returns total physical memory in bytes
total = int(
subprocess.check_output(["sysctl", "-n", "hw.memsize"], stderr=subprocess.DEVNULL)
.decode()
.strip()
)
# vm_page_free_count + vm_page_speculative_count gives available pages
page_size = int(
subprocess.check_output(["sysctl", "-n", "hw.pagesize"], stderr=subprocess.DEVNULL)
.decode()
.strip()
)
free_pages = int(
subprocess.check_output(["sysctl", "-n", "vm.page_free_count"], stderr=subprocess.DEVNULL)
.decode()
.strip()
)
speculative_pages = int(
subprocess.check_output(["sysctl", "-n", "vm.page_speculative_count"], stderr=subprocess.DEVNULL)
.decode()
.strip()
)
available = (free_pages + speculative_pages) * page_size
used = total - available
pct = (used / total) * 100 if total > 0 else 0
return _format_memory_result(pct, used, total)
except (subprocess.SubprocessError, FileNotFoundError, ValueError, OSError):
return None


def _memory_usage_from_psutil() -> Optional[Tuple[str, str, float]]:
"""Use psutil if available for cross-platform memory info."""
try:
import psutil # type: ignore
mem = psutil.virtual_memory()
return _format_memory_result(mem.percent, mem.used, mem.total)
except ImportError:
return None


def _format_memory_result(pct: float, used: int, total: int) -> Tuple[str, str, float]:
used_gb = used / (1024 ** 3)
total_gb = total / (1024 ** 3)
if pct < MEMORY_THRESHOLD_WARNING:
return "OK", f"{pct:.1f}% used ({used_gb:.1f}GB/{total_gb:.1f}GB)", pct
elif pct < MEMORY_THRESHOLD_CRITICAL:
return "WARNING", f"{pct:.1f}% used ({used_gb:.1f}GB/{total_gb:.1f}GB)", pct
else:
return "CRITICAL", f"{pct:.1f}% used ({used_gb:.1f}GB/{total_gb:.1f}GB)", pct


def check_memory_usage() -> Tuple[str, str, float]:
"""
Check memory usage with cross-platform fallbacks.

Priority:
1. /proc/meminfo (Linux)
2. psutil (cross-platform, if installed)
3. sysctl (macOS/BSD)
4. WARNING with explanation if all fail
"""
result = _memory_usage_from_proc()
if result is not None:
return result

result = _memory_usage_from_psutil()
if result is not None:
return result

result = _memory_usage_from_sysctl()
if result is not None:
return result

return "WARNING", "Memory check unavailable (no /proc/meminfo, psutil, or sysctl)", 0


def check_load_average() -> Tuple[str, str, float]:
"""
Check system load average with cross-platform fallback.

Priority:
1. /proc/loadavg (Linux)
2. os.getloadavg() (POSIX — macOS, BSD, Linux)
3. WARNING with explanation if all fail
"""
try:
with open("/proc/loadavg") as f:
parts = f.read().strip().split()
load = float(parts[0])
except (FileNotFoundError, IOError, OSError):
try:
load_avgs = os.getloadavg()
load = load_avgs[0]
except (AttributeError, OSError):
return "WARNING", "Load check unavailable (no /proc/loadavg or os.getloadavg())", 0

cpu_count = os.cpu_count() or 1
load_pct = (load / cpu_count) * 100

if load_pct < 70:
return "OK", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load
elif load_pct < 90:
return "WARNING", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load
else:
return "CRITICAL", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load


def check_http_service(host: str, port: int, path: str, timeout: int) -> Tuple[str, str, int]:
import http.client
try:
Expand Down Expand Up @@ -149,57 +295,11 @@ def check_disk_usage(path: str = "/") -> Tuple[str, str, float]:
return "WARNING", f"Cannot check: {e}", 0


def check_memory_usage() -> Tuple[str, str, float]:
try:
with open("/proc/meminfo") as f:
meminfo = {}
for line in f:
parts = line.split(":")
if len(parts) == 2:
key = parts[0].strip()
value = parts[1].strip().replace(" kB", "")
try:
meminfo[key] = int(value) * 1024
except ValueError:
pass

total = meminfo.get("MemTotal", 0)
available = meminfo.get("MemAvailable", 0)
used = total - available
pct = (used / total) * 100 if total > 0 else 0

if pct < MEMORY_THRESHOLD_WARNING:
return "OK", f"{pct:.1f}% used ({used // (1024**3)}GB/{total // (1024**3)}GB)", pct
elif pct < MEMORY_THRESHOLD_CRITICAL:
return "WARNING", f"{pct:.1f}% used", pct
else:
return "CRITICAL", f"{pct:.1f}% used", pct
except Exception as e:
return "WARNING", f"Cannot check: {e}", 0


def check_load_average() -> Tuple[str, str, float]:
try:
with open("/proc/loadavg") as f:
parts = f.read().strip().split()
load = float(parts[0])
cpu_count = os.cpu_count() or 1
load_pct = (load / cpu_count) * 100

if load_pct < 70:
return "OK", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load
elif load_pct < 90:
return "WARNING", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load
else:
return "CRITICAL", f"Load: {load} ({load_pct:.0f}% of {cpu_count} cores)", load
except Exception as e:
return "WARNING", f"Cannot check: {e}", 0


# ---------------------------------------------------------------------------
# HEALTH CHECK RUNNER
# ---------------------------------------------------------------------------


def run_health_checks(service: Optional[str] = None, json_output: bool = False) -> Dict[str, Any]:
results: Dict[str, Any] = {
"timestamp": datetime.now().isoformat(),
Expand Down Expand Up @@ -348,4 +448,4 @@ def main():


if __name__ == "__main__":
main()
main()
20 changes: 17 additions & 3 deletions tools/log_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def extract_level(self, line: str) -> str:
return 'unknown'

def extract_service(self, line: str) -> Optional[str]:
match = re.search(r'\[(\w+)\]', line)
match = re.search(r'\[([\w-]+)\]', line)
if match:
return match.group(1)
match = re.search(r'(\w+)\s*:', line)
Expand All @@ -121,9 +121,23 @@ def parse(self, line: str) -> Optional[Dict[str, Any]]:
entry = json.loads(line.strip())
if not isinstance(entry, dict):
return None
raw_level = (entry.get('level') or entry.get('severity') or entry.get('lvl', 'info'))
raw_ts = entry.get('timestamp') or entry.get('time') or entry.get('@timestamp')
# Normalize level to lowercase
level = raw_level.lower() if isinstance(raw_level, str) else 'info'
# Convert ISO timestamp string to int if possible
timestamp = raw_ts
if isinstance(raw_ts, str):
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S']:
try:
dt = datetime.strptime(raw_ts[:19], fmt)
timestamp = int(dt.replace(tzinfo=timezone.utc).timestamp())
break
except (ValueError, IndexError):
continue
return {
'timestamp': entry.get('timestamp') or entry.get('time') or entry.get('@timestamp'),
'level': entry.get('level') or entry.get('severity') or entry.get('lvl', 'info'),
'timestamp': timestamp,
'level': level,
'service': entry.get('service') or entry.get('logger') or entry.get('app'),
'message': entry.get('message') or entry.get('msg') or entry.get('event', ''),
'fields': entry,
Expand Down
Loading