From e616aed4355c0330f8181aaabecf91e7b4cecd64 Mon Sep 17 00:00:00 2001 From: Samarth1306w Date: Fri, 19 Jun 2026 22:58:08 +0530 Subject: [PATCH] Implement cross-platform health check fallbacks for memory and load checks --- build.py | 44 ++++---- diagnostic/build-bf2147ac-metadata.json | 75 ++++++++++++++ diagnostic/build-bf2147ac.logd | Bin 0 -> 2941 bytes tools/encryptly/linux-arm64/encryptly | Bin 133136 -> 1840 bytes tools/health_check.py | 129 +++++++++++++++++++----- tools/test_health_check_fallback.py | 108 ++++++++++++++++++++ 6 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 diagnostic/build-bf2147ac-metadata.json create mode 100644 diagnostic/build-bf2147ac.logd create mode 100644 tools/test_health_check_fallback.py diff --git a/build.py b/build.py index fb33aa14..b3061c30 100644 --- a/build.py +++ b/build.py @@ -185,7 +185,7 @@ def _normalize_arch(machine: str) -> Optional[str]: def _normalize_os() -> Optional[str]: system = platform.system().lower() - if system == "linux": + if system in {"linux", "android"}: return "linux" if system == "darwin": return "macos" @@ -288,28 +288,32 @@ def build_module( return False, time.time() - start, f"npm install failed:\n{install_result.stderr}" except subprocess.TimeoutExpired: return False, time.time() - start, "npm install TIMEOUT (120s)" + except FileNotFoundError as e: + return False, time.time() - start, f"Command not found: {e}" if module.name == "engine": - build_type = "Release" if release else "Debug" - cfg_result = subprocess.run( - ["cmake", "-S", ".", "-B", "build", - f"-DCMAKE_BUILD_TYPE={build_type}"], - cwd=str(module.dir), - capture_output=True, - text=True, - timeout=120, - env=env, - ) - if cfg_result.returncode != 0: - return False, time.time() - start, ( - f"CMake configure failed:\n{cfg_result.stderr}") - if verbose: - print(f" {color('cmake configured', Colors.GRAY)}") - cmd = ["cmake", "--build", "build"] - if release: - cmd.append("--config") - cmd.append("Release") + try: + cfg_result = subprocess.run( + ["cmake", "-S", ".", "-B", "build", + f"-DCMAKE_BUILD_TYPE={build_type}"], + cwd=str(module.dir), + capture_output=True, + text=True, + timeout=120, + env=env, + ) + if cfg_result.returncode != 0: + return False, time.time() - start, ( + f"CMake configure failed:\n{cfg_result.stderr}") + if verbose: + print(f" {color('cmake configured', Colors.GRAY)}") + cmd = ["cmake", "--build", "build"] + if release: + cmd.append("--config") + cmd.append("Release") + except FileNotFoundError as e: + return False, time.time() - start, f"Command not found: {e}" else: cmd = list(module.build_cmd) if release and module.name == "backend": diff --git a/diagnostic/build-bf2147ac-metadata.json b/diagnostic/build-bf2147ac-metadata.json new file mode 100644 index 00000000..b5e976e0 --- /dev/null +++ b/diagnostic/build-bf2147ac-metadata.json @@ -0,0 +1,75 @@ +{ + "generated_at": "2026-06-19T17:28:00.923428+00:00", + "commit": "bf2147ac", + "diagnostic_logd": "diagnostic/build-bf2147ac.logd", + "chunked": false, + "chunk_size_bytes": null, + "password": "Packed /data/data/com.termux/files/home/.cache/tent-of-trials/logd-workspace into /data/data/com.termux/files/home/weilixiong-tentoftrials/diagnostic/build-bf2147ac.logd", + "decrypt_command": "encryptly unpack diagnostic/build-bf2147ac.logd --password Packed /data/data/com.termux/files/home/.cache/tent-of-trials/logd-workspace into /data/data/com.termux/files/home/weilixiong-tentoftrials/diagnostic/build-bf2147ac.logd", + "total_modules": 10, + "passed": 1, + "failed": 9, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "frontend", + "status": "FAIL", + "elapsed_seconds": 1.205, + "artifact": null + }, + { + "name": "market", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "frailbox", + "status": "PASS", + "elapsed_seconds": 0.051, + "artifact": "/data/data/com.termux/files/home/weilixiong-tentoftrials/frailbox/frailbox" + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 0.004, + "artifact": null + }, + { + "name": "compliance", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "v2-market-stream", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "nfc-scanner", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + }, + { + "name": "openapi-tools", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null + } + ], + "pr_note": "Include this metadata and diagnostic/build-bf2147ac.logd in your PR. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/diagnostic/build-bf2147ac.logd b/diagnostic/build-bf2147ac.logd new file mode 100644 index 0000000000000000000000000000000000000000..5c6d194bbb4da87088e61f6d0a9e15d9f9c5da31 GIT binary patch literal 2941 zcmZ`*XH*kP8Vy}a0D;gE5KyXubOfYI2@s?N0#Y@E5{UHr1Vo6TH|a$X6a?u>ctAu- zP?`v+^b$adbm`UAw`b4pb9ZNcd}n6P+#mOT-?`TmOhL&8001rkH1NZB7sUZ%RWtyA zJvRXG>(&en{oO&;q$jN@~RVE3Kc zDB5$ZSr4<}r6WRF>^ysu*Zpp^p6ibpn75XiKc96q+mtQ%R>sS36~SsDP#CJT+GZ@;;X{^Q=~IW>dWOaX#H(p}JOwC0QCipW>g6?8og|z=g~`5ru{Kj;#&$3wEcCSUxu(i|;y$h#ACzxhcY|#}slrD4 zZHXEymo0+%ycLx$F6?~{exq+D-<^om>B3kDgXpv_e|FM+xZZX;{%A=R?cmX8^F>l< zih?Hl(g+VRxcOCp5ou}FrM{LWV{L0EO(ZT$yV8MwW*4Q459pt5coE6ZKhn8p;^7`s z9cpv{mAqtc9UQ}%wa+D>_L@)jQU7?Mg~h;l?kxdIOWR9RI|2_MdtBIh@xwIPO~46w4YqAwm&5 z5*XF27`juet|*XJw>#J^>P7sMtY#!tS%2m&I*+SRQiPyur$ zeNlgq_TnrCMl3~Maf`m7*jra$W|8!3zd5I~3~Up@JLX-K!NzAxdh%XE7)xY2~;+m$6} z5+)ar`VhI_qF?$A#k3XC(Fj_ai1Zt}QqJKsVA;iZ`v7G{_L_4H<$rQ}*Jb8jZQZF` zz8QwFeUWHX8F_T;+1@ZoLtWy}9;$_M$ybG6W<;O)8||LAY1-%&@!{w0)_+aAdVb~? zfbdtduc#KU-mLelO8ByQVIyel4J+QEhrey)>aon4cRRgcj-M)jw32=jo5v*Rs$WKb`kW}K zQ*4cm!tjrgq&HX}hp&7yQ1K#43T$<`wfJP>Hl^0z@{KOB>P@^fNd1hQgJLOJRlwQM%6n0)9_28UV= zXK#{C9V>wjKTuH|XnnSdY5N>*@{e^ z7pf~a{1>Q8bK`gdo%bhWcgH51Hzo{q4tV#2z(gN^(#RL0;eKvLwtO4aa6%Qv^EVNl2>ywHaI32 z7O0?1!8t%oKbSY^Yx~qPy>h39Vu#vMojTtcRhr2y2_9MTdqh=|%e}VcrEW=8qkSWP z=b;|3L4?Yf*1ZwC0iuZ^fIg39ycj3U*U^> zF7qR$E(Z&H!^@t`PvQvu`0Omsj(f;g$T0^!VPq-MNFx^uc_-Cbsd04*T=0z!&5q+Z z)HTa==}^fz?eTCG9jL=T;!+{gPLg?Sk(;G8QM-qvvjfAT(3Nssi+5Vq<5|AjVgpU< z3ve$OeW*P*^!FAtuGpOAMb8@-8pSuu>Ze6r&)8pHQ=|A|Ofo;9=~TLQk#>L2fRURL zyKS?$+MLZnVrj*_<;3MGK8RK)OG3ri<~iGXZMSr9LB#o?4G-jo9XzXfQc53lFbBgH z4qpnK7Mzd9Irl8r+)eg#u4i>QhA@vi)*g+#k{i{nlMm;J!+4sViFy9p zjX^C(66fg(M)C!4FqTEF5&gCQ{L+pKLOZc7 zu!fe~n35JBr65&F;~~?6$qnbPy{bo+Juc+~EEVT4le&M$GZt=VKVk^)^-*_#W*U}i z)ga_-S0~y`D~n8v-*J{3oB5UXqhCGd1Tu><#@v$v1dQ@_i-h2t!XxOPih&R-1p4W1 zMbrCVFASj*J6jLUdp;Dp2i<)=f_MNzY^CGhE7>RDxy)-i#5RX?WNrn4k0jilL&lp4 zM!{=0)i{mTDKn2_+WZsZflUn?v^#3bYgkoB z)l`{qpZ)31!d9yoN*}i0PW(PHE=o%`=d1okSL{dA9Uf zRdII4i0${Lp(bsZIj1V zicSWz2>SJQL}*tw~zeO`hJa zAH@zX75c{>7gX%?C{bFBFtX2Oeroy3w8f9=WkZN1|)#<;7Divdm>HGjDZ{03TY$b-<5!; z!}da?XX27QAaLo8n{b;g29RcdjFu9+*(a6$Vq}_|rD^tdx6y|=ar$g8td`KyLU;#X zkJ9;+A#Y*agJ~8<*`}I%{!e6`xW)2x?!Eu;zWUuLOAweEZ|JuiG8FQmu;Sz;)j;Lc zq(~UR6ii0WM)Ch+>Q4dwS`m@I%Kr+jza#uU2><*K07OL2{#5iYg#Q|g-!Xpgfq!F& b(f%jKuZ}PUQ&InVr}(+sf7VjEU#EWnVw_i& literal 0 HcmV?d00001 diff --git a/tools/encryptly/linux-arm64/encryptly b/tools/encryptly/linux-arm64/encryptly index 16dab211c9431b012c670b5f3a65ea1e302249d5..88efb849be81559b6d4cf65a027983a0347ed3a3 100755 GIT binary patch literal 1840 zcmb7FO>f&U488kTaOt5Ame^~13z7oE+5!W%p@;UeqA(O&Nd?=IAt_DLqW`_5WXsNw zVXF`R5XndCJ&Hd0lore~f6G;!Sgz~ME?tR=n{;Dzx)d^P_V!81i%8Us(iY6#bgwGB zy@-Z_qLo%PG|WC#VhL@)Eq+E(!B;8Z0=&cuDlTJ0OPF3ik6{ky-a^(!3u)ug zyu}kj^3m6gD zs84+Y=x1gw70fHOE;8ECB}}J6=GCU)FyAU&8jRz#TC?4hHa|7u1#NV{3}uCQtbvFK z8-6%{>T=V#VvzAKQK3zy@N9Tihgaz&`)I8XrcSd)= zn>OiTapLpYpu8?(sW~e{W!-96!2D9#A#TqqIk%!-YpwJQehOoRTn}g^ng@zf$T3xE zJ>)!rZ&;M&D3|8a0y4;RtMnceHdrSfQA4F+gBWWzf~6 z@2;=Lz>G5-@=7hDm9i7sy_vY&hFV6MWXq~D9u)CM%Mh0o?#jgodYVIYa-oY%?j&@b zOex2ZIz!l-_w{$CbIEFs{vkHeykb^7^USJ)vW@}}N6A(T%j1AB!Q<&*dntxF1CDRD z)#w&aH!t9Iz`+}GV->sx%HIZE7dq}spTL3g3Y(~#8GZx}4plcb+SxRm8*{a>C=88? zh8csiqkwt$_R&e)vorPSUwY6<#dlV-+_I{QeeRw@(kK+x zX>;!eIjPf2(Q4!4|5F&l`a%`$4%EOT=+%jjQ~ LWv#;g#}&N;615RN literal 133136 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 Tuple[str, str, float]: 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 + import platform + # 1. Try /proc/meminfo first (Linux/Android) + if os.path.exists("/proc/meminfo"): + 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) + if total > 0 and available == 0: + free = meminfo.get("MemFree", 0) + buffers = meminfo.get("Buffers", 0) + cached = meminfo.get("Cached", 0) + available = free + buffers + cached + used = total - available + pct = (used / total) * 100 if total > 0 else 0 + else: + # 2. Non-Linux fallbacks + system = platform.system().lower() + if "darwin" in system: + # macOS standard library fallback using subprocess to run sysctl and vm_stat + import subprocess + total_str = subprocess.check_output(["sysctl", "-n", "hw.memsize"]).strip() + total = int(total_str) + vm_stat_out = subprocess.check_output(["vm_stat"]).decode("utf-8") + vm_stats = {} + page_size = 4096 + for line in vm_stat_out.splitlines(): + if "page size of" in line: + parts = line.split("page size of") + if len(parts) > 1: + page_size = int(parts[1].split()[0]) + elif ":" in line: + parts = line.split(":") + key = parts[0].strip() + val = parts[1].strip().rstrip(".") + try: + vm_stats[key] = int(val) + except ValueError: + pass + + free_pages = vm_stats.get("Pages free", 0) + inactive_pages = vm_stats.get("Pages inactive", 0) + speculative_pages = vm_stats.get("Pages speculative", 0) + available = (free_pages + inactive_pages + speculative_pages) * page_size + used = total - available + pct = (used / total) * 100 if total > 0 else 0 + elif "windows" in system: + # Windows standard library fallback using ctypes (kernel32.GlobalMemoryStatusEx) + import ctypes + class MEMORYSTATUSEX(ctypes.Structure): + _fields_ = [ + ("dwLength", ctypes.c_ulong), + ("dwMemoryLoad", ctypes.c_ulong), + ("ullTotalPhys", ctypes.c_ulonglong), + ("ullAvailPhys", ctypes.c_ulonglong), + ("ullTotalPageFile", ctypes.c_ulonglong), + ("ullAvailPageFile", ctypes.c_ulonglong), + ("ullTotalVirtual", ctypes.c_ulonglong), + ("ullAvailVirtual", ctypes.c_ulonglong), + ("ullAvailExtendedVirtual", ctypes.c_ulonglong), + ] + stat = MEMORYSTATUSEX() + stat.dwLength = ctypes.sizeof(MEMORYSTATUSEX) + ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) + total = stat.ullTotalPhys + available = stat.ullAvailPhys + used = total - available + pct = float(stat.dwMemoryLoad) + else: + raise NotImplementedError("Platform not supported for memory check fallback") if pct < MEMORY_THRESHOLD_WARNING: return "OK", f"{pct:.1f}% used ({used // (1024**3)}GB/{total // (1024**3)}GB)", pct @@ -180,18 +242,29 @@ def check_memory_usage() -> Tuple[str, str, float]: 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 + load = None + # 1. Try /proc/loadavg first (Linux/Android) + if os.path.exists("/proc/loadavg"): + with open("/proc/loadavg") as f: + parts = f.read().strip().split() + load = float(parts[0]) + + # 2. Fallback to os.getloadavg() if available + if load is None and hasattr(os, "getloadavg"): + load = os.getloadavg()[0] + + if load is None: + raise NotImplementedError("Load average not supported on this platform") + + 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 diff --git a/tools/test_health_check_fallback.py b/tools/test_health_check_fallback.py new file mode 100644 index 00000000..f7f5676e --- /dev/null +++ b/tools/test_health_check_fallback.py @@ -0,0 +1,108 @@ +import unittest +from unittest.mock import patch, MagicMock +import os +import platform +import sys + +# Add parent directory to path so we can import health_check +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import tools.health_check as hc + +class TestHealthCheckFallback(unittest.TestCase): + + @patch('os.path.exists') + @patch('platform.system') + @patch('subprocess.check_output') + def test_darwin_memory_fallback(self, mock_check_output, mock_system, mock_exists): + # Setup mocks + mock_exists.return_value = False + mock_system.return_value = "Darwin" + + # Mock sysctl hw.memsize returning 16GB + # Mock vm_stat returning statistics + def check_output_side_effect(cmd, *args, **kwargs): + if "sysctl" in cmd: + return b"17179869184\n" + elif "vm_stat" in cmd: + return ( + b"Mach Virtual Memory Statistics: (page size of 4096 bytes)\n" + b"Pages free: 1000000.\n" + b"Pages active: 2000000.\n" + b"Pages inactive: 1000000.\n" + b"Pages speculative: 200000.\n" + ) + raise ValueError(f"Unexpected subprocess call: {cmd}") + + mock_check_output.side_effect = check_output_side_effect + + status, detail, val = hc.check_memory_usage() + + self.assertEqual(status, "OK") + # 47.5% used (used memory = 16GB - 2.2M pages * 4096 = 17179869184 - 9011200000 = 8168669184 = 7.6GB used out of 16GB) + self.assertIn("47.5% used", detail) + self.assertAlmostEqual(val, 47.548, places=2) + + @patch('os.path.exists') + @patch('platform.system') + @patch('os.cpu_count') + def test_darwin_load_fallback(self, mock_cpu_count, mock_system, mock_exists): + mock_exists.return_value = False + mock_system.return_value = "Darwin" + mock_cpu_count.return_value = 4 + + # Mock os.getloadavg to return load values + with patch('os.getloadavg', return_value=(1.5, 1.2, 1.0), create=True): + status, detail, val = hc.check_load_average() + + self.assertEqual(status, "OK") + self.assertIn("Load: 1.5 (38% of 4 cores)", detail) + self.assertEqual(val, 1.5) + + @patch('os.path.exists') + @patch('platform.system') + def test_windows_memory_fallback(self, mock_system, mock_exists): + mock_exists.return_value = False + mock_system.return_value = "Windows" + + # Let's mock ctypes completely + mock_ctypes = MagicMock() + + # Dummy structure fields + class MockStructure: + dwLength = 0 + dwMemoryLoad = 40 + ullTotalPhys = 17179869184 + ullAvailPhys = 10307921510 + + mock_ctypes.Structure = object + + with patch.dict('sys.modules', {'ctypes': mock_ctypes}): + # Set structure return value + mock_ctypes.sizeof.return_value = 64 + + # Since MEMORYSTATUSEX is defined inside check_memory_usage, we will mock ctypes + # in sys.modules. We need to make sure MEMORYSTATUSEX instantiation returns our mocked struct + # and windll.kernel32.GlobalMemoryStatusEx is called. + # To do this cleanly, we'll configure mock_ctypes: + # MEMORYSTATUSEX is defined as a subclass of ctypes.Structure + # When MEMORYSTATUSEX() is called, it returns a new instance. + # We can capture the instance or mock the __new__ or __init__ of Structure + # Or even simpler, mock_ctypes.Structure can be a class that has the fields we want! + class DummyStructure(object): + def __init__(self, *args, **kwargs): + self.dwLength = 0 + self.dwMemoryLoad = 40 + self.ullTotalPhys = 17179869184 + self.ullAvailPhys = 10307921510 + + mock_ctypes.Structure = DummyStructure + mock_ctypes.sizeof.return_value = 64 + + status, detail, val = hc.check_memory_usage() + + # Verify status code is OK and uses 40% memory load from dwMemoryLoad + self.assertEqual(status, "OK") + self.assertEqual(val, 40.0) + +if __name__ == '__main__': + unittest.main()