diff --git a/build.py b/build.py index 9b82104b..24bd46ab 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" diff --git a/diagnostic/build-2b54872c.json b/diagnostic/build-2b54872c.json new file mode 100644 index 00000000..ea30220f --- /dev/null +++ b/diagnostic/build-2b54872c.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-20T05:45:33.867184+00:00", + "commit": "2b54872c", + "diagnostic_logd": "diagnostic/build-2b54872c.logd", + "diagnostic_logd_error": null, + "chunked": false, + "chunk_size_bytes": null, + "password": "7af65ddb5df46a8c00ab", + "decrypt_command": "encryptly unpack diagnostic/build-2b54872c.logd --password 7af65ddb5df46a8c00ab", + "total_modules": 10, + "passed": 1, + "failed": 9, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cargo'" + }, + { + "name": "frontend", + "status": "FAIL", + "elapsed_seconds": 1.434, + "artifact": null, + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\nsh: 1: tsc: not found" + }, + { + "name": "market", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'go'" + }, + { + "name": "frailbox", + "status": "PASS", + "elapsed_seconds": 0.063, + "artifact": "/data/data/com.termux/files/home/weilixiong-zeroeye/frailbox/frailbox", + "output": "make: Nothing to be done for 'all'." + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cmake'" + }, + { + "name": "compliance", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'javac'" + }, + { + "name": "v2-market-stream", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ruby'" + }, + { + "name": "nfc-scanner", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ghc'" + }, + { + "name": "openapi-tools", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-2b54872c.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/diagnostic/build-2b54872c.logd b/diagnostic/build-2b54872c.logd new file mode 100644 index 00000000..4b1304d2 Binary files /dev/null and b/diagnostic/build-2b54872c.logd differ diff --git a/tools/encryptly/linux-arm64/encryptly b/tools/encryptly/linux-arm64/encryptly index 54631019..254cf7dc 100755 Binary files a/tools/encryptly/linux-arm64/encryptly and b/tools/encryptly/linux-arm64/encryptly differ diff --git a/tools/monitoring_setup.py b/tools/monitoring_setup.py index 65f43d20..8238d1a0 100644 --- a/tools/monitoring_setup.py +++ b/tools/monitoring_setup.py @@ -25,6 +25,7 @@ import argparse import json import os +import re import sys import time import urllib.request @@ -78,7 +79,7 @@ }, { "name": "HighMemoryUsage", - "expr": "process_resident_memory_bytes / process_resident_memory_bytes > 0.9", + "expr": "process_resident_memory_bytes / machine_memory_bytes > 0.9", "duration": "10m", "severity": "warning", "summary": "High memory usage on {{$labels.instance}}", @@ -372,6 +373,26 @@ def backup_monitoring_config(output_dir: str, prometheus_url: str, return True +def validate_alert_expressions(rules: List[Dict[str, Any]]) -> bool: + """ + Validate alert rules configuration for self-dividing expressions. + Returns False if any self-dividing expressions are found, True otherwise. + """ + self_dividing_pattern = re.compile(r'\b(\w+)\s*/\s*\1\b') + all_valid = True + for rule in rules: + name = rule.get("name") + expr = rule.get("expr") + if expr: + match = self_dividing_pattern.search(expr) + if match: + print(f" ✗ Validation Alert: Alert rule '{name}' has a self-dividing expression: '{expr}' (divided by '{match.group(1)}')") + all_valid = False + else: + print(f" ✓ Alert rule '{name}' expression is valid") + return all_valid + + def parse_args(): parser = argparse.ArgumentParser(description="Monitoring setup tool") parser.add_argument("--prometheus-url", default=DEFAULT_PROMETHEUS_URL) @@ -429,19 +450,20 @@ def main(): if args.validate: print("Validating monitoring configuration...") + alerts_ok = validate_alert_expressions(RECOMMENDED_ALERT_RULES) configs_to_check = [ args.prometheus_url, args.alertmanager_url, ] - all_ok = True + endpoints_ok = True for url in configs_to_check: result = http_request("GET", f"{url}/-/healthy") if result: print(f" {url}: OK") else: print(f" {url}: FAILED") - all_ok = False - return 0 if all_ok else 1 + endpoints_ok = False + return 0 if (alerts_ok and endpoints_ok) else 1 parser.print_help() return 0 diff --git a/tools/test_monitoring.py b/tools/test_monitoring.py new file mode 100644 index 00000000..664f34a1 --- /dev/null +++ b/tools/test_monitoring.py @@ -0,0 +1,55 @@ +""" +Unit tests for monitoring configuration and alert rule validations. +""" + +import sys +import os +import unittest + +# Add parent directory to path so we can import tools.monitoring_setup +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from tools import monitoring_setup + +class TestMonitoringValidation(unittest.TestCase): + """ + Test suite for checking monitoring alert rule expressions and validation helper. + """ + + def test_self_dividing_detection(self): + """ + Verify that self-dividing PromQL expressions are flagged and correct ones pass. + """ + # Self-dividing expression (should fail validation) + invalid_rules = [ + { + "name": "InvalidMemoryRule", + "expr": "process_resident_memory_bytes / process_resident_memory_bytes > 0.9" + } + ] + + # Valid expression (should pass validation) + valid_rules = [ + { + "name": "ValidMemoryRule", + "expr": "process_resident_memory_bytes / machine_memory_bytes > 0.9" + }, + { + "name": "ValidCpuRule", + "expr": "avg by(instance) (rate(process_cpu_seconds_total[5m])) > 0.8" + } + ] + + # Check validation outcomes + self.assertFalse(monitoring_setup.validate_alert_expressions(invalid_rules)) + self.assertTrue(monitoring_setup.validate_alert_expressions(valid_rules)) + + def test_recommended_rules_valid(self): + """ + Ensure all production alert rules defined in monitoring_setup are valid. + """ + # Run validation on the actual recommended alert rules in the codebase + rules = monitoring_setup.RECOMMENDED_ALERT_RULES + self.assertTrue(monitoring_setup.validate_alert_expressions(rules)) + +if __name__ == '__main__': + unittest.main()