diff --git a/build.py b/build.py index 9b82104b..85f10db5 100644 --- a/build.py +++ b/build.py @@ -720,7 +720,7 @@ def main(): print(f"\n {color('⚠ Some tools missing - will try anyway:', Colors.YELLOW)}") for m in missing: print(f" {m}") - print(f" {color('Not all modules will build. That\'s fine.', Colors.GRAY)}") + print(" " + color("Not all modules will build. That's fine.", Colors.GRAY)) else: print(f" {color('✓ All prerequisites found', Colors.GREEN)}") diff --git a/diagnostic/build-2b54872c.json b/diagnostic/build-2b54872c.json new file mode 100644 index 00000000..fcfbff36 --- /dev/null +++ b/diagnostic/build-2b54872c.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-19T03:29:47.287298+00:00", + "commit": "2b54872c", + "diagnostic_logd": "diagnostic/build-2b54872c.logd", + "diagnostic_logd_error": null, + "chunked": false, + "chunk_size_bytes": null, + "password": "9518dae8e3644cbfbf97", + "decrypt_command": "encryptly unpack diagnostic/build-2b54872c.logd --password 9518dae8e3644cbfbf97", + "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": "PASS", + "elapsed_seconds": 12.151, + "artifact": "/mnt/c/Users/jarlyn/Documents/make money/zeroeye-memory-bounty/frontend/dist", + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\n\u001b[36mvite v6.4.3 \u001b[32mbuilding for production...\u001b[36m\u001b[39m\ntransforming...\n\u001b[32m\u2713\u001b[39m 100 modules transformed.\nrendering chunks...\ncomputing gzip size...\n\u001b[2mdist/\u001b[22m\u001b[32mindex.html \u001b[39m\u001b[1m\u001b[2m 0.63 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 0.35 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/state-BkjSKDbY.js \u001b[39m\u001b[1m\u001b[2m 8.91 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 3.54 kB\u001b[22m\u001b[2m \u2502 map: 57.15 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/vendor-CREcWLHI.js \u001b[39m\u001b[1m\u001b[2m 48.93 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 17.25 kB\u001b[22m\u001b[2m \u2502 map: 481.27 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/index-CyxcoTyU.js \u001b[39m\u001b[1m\u001b[2m231.32 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 72.16 kB\u001b[22m\u001b[2m \u2502 map: 1,045.57 kB\u001b[22m\n\u001b[32m\u2713 built in 1.22s\u001b[39m" + }, + { + "name": "market", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'go'" + }, + { + "name": "frailbox", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'make'" + }, + { + "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..780be963 Binary files /dev/null and b/diagnostic/build-2b54872c.logd differ diff --git a/tools/monitoring_setup.py b/tools/monitoring_setup.py index 65f43d20..68ae7472 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}}", @@ -197,9 +198,40 @@ def check_alertmanager(url: str) -> bool: return False +METRIC_REF_RE = r"[A-Za-z_:][A-Za-z0-9_:]*(?:\s*\{[^{}]*\})?(?:\s*\[[^\[\]]+\])?" +DIVISION_RE = re.compile( + rf"(?{METRIC_REF_RE})\s*/\s*(?P{METRIC_REF_RE})(?![A-Za-z0-9_:])" +) + + +def _normalize_metric_ref(metric_ref: str) -> str: + return re.sub(r"\s+", "", metric_ref) + + +def validate_alert_rule_expressions(rules: List[Dict[str, Any]]) -> List[str]: + errors = [] + for rule in rules: + name = rule.get("name", "") + expr = str(rule.get("expr", "")) + for match in DIVISION_RE.finditer(expr): + left = _normalize_metric_ref(match.group("left")) + right = _normalize_metric_ref(match.group("right")) + if left == right: + errors.append(f"{name}: expression divides {left} by itself") + break + return errors + + def upload_prometheus_rules(rules: List[Dict[str, Any]], prometheus_url: str, dry_run: bool = False) -> bool: + validation_errors = validate_alert_rule_expressions(rules) + if validation_errors: + print("Invalid alert rule expressions:", file=sys.stderr) + for error in validation_errors: + print(f" - {error}", file=sys.stderr) + return False + rules_file = "/etc/prometheus/rules/tent_rules.yml" print(f"{'Would upload' if dry_run else 'Uploading'} {len(rules)} rules to {prometheus_url}") @@ -414,13 +446,15 @@ def main(): args.alertmanager_url, args.slack_webhook, args.pagerduty_key, args.dry_run) - upload_prometheus_rules(RECOMMENDED_ALERT_RULES, args.prometheus_url, args.dry_run) + if not upload_prometheus_rules(RECOMMENDED_ALERT_RULES, args.prometheus_url, args.dry_run): + return 1 print("Monitoring initialization complete") return 0 if args.alerts: - upload_prometheus_rules(RECOMMENDED_ALERT_RULES, args.prometheus_url, args.dry_run) - return 0 + return 0 if upload_prometheus_rules( + RECOMMENDED_ALERT_RULES, args.prometheus_url, args.dry_run + ) else 1 if args.backup: return 0 if backup_monitoring_config( diff --git a/tools/test_monitoring_setup.py b/tools/test_monitoring_setup.py new file mode 100644 index 00000000..29a4eca2 --- /dev/null +++ b/tools/test_monitoring_setup.py @@ -0,0 +1,40 @@ +import unittest + +from tools import monitoring_setup + + +class AlertRuleValidationTests(unittest.TestCase): + def test_recommended_alert_rules_do_not_self_divide(self): + errors = monitoring_setup.validate_alert_rule_expressions( + monitoring_setup.RECOMMENDED_ALERT_RULES + ) + + self.assertEqual([], errors) + + def test_high_memory_usage_uses_machine_memory_denominator(self): + rule = next( + rule + for rule in monitoring_setup.RECOMMENDED_ALERT_RULES + if rule["name"] == "HighMemoryUsage" + ) + + self.assertIn("process_resident_memory_bytes", rule["expr"]) + self.assertIn("machine_memory_bytes", rule["expr"]) + + def test_self_dividing_expression_is_rejected(self): + errors = monitoring_setup.validate_alert_rule_expressions( + [ + { + "name": "BrokenMemoryUsage", + "expr": "process_resident_memory_bytes / process_resident_memory_bytes > 0.9", + } + ] + ) + + self.assertEqual(1, len(errors)) + self.assertIn("BrokenMemoryUsage", errors[0]) + self.assertIn("process_resident_memory_bytes", errors[0]) + + +if __name__ == "__main__": + unittest.main()