aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/check_profile.py
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/check_profile.py
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/check_profile.py')
-rw-r--r--prototypes/connections/check_profile.py82
1 files changed, 82 insertions, 0 deletions
diff --git a/prototypes/connections/check_profile.py b/prototypes/connections/check_profile.py
new file mode 100644
index 0000000..ac2ba00
--- /dev/null
+++ b/prototypes/connections/check_profile.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+"""Check the selected default hosts' linked code and direct dependencies.
+
+This is a Linux/GNU release-build gate, separate from sanitizer and core checks.
+It does not estimate process RAM or the cost of optional UI/network hosts.
+"""
+import argparse
+import hashlib
+import json
+from pathlib import Path
+import platform
+import re
+import subprocess
+
+ROOT = Path(__file__).resolve().parent
+NEEDED = re.compile(r"\(NEEDED\).*Shared library: \[([^]]+)\]")
+
+
+def run(*args):
+ return subprocess.check_output([str(arg) for arg in args], text=True)
+
+
+def inspect(path):
+ rows = [row.split() for row in run('size', path).splitlines()]
+ if len(rows) != 2 or len(rows[1]) < 6 or not all(
+ rows[1][i].isdigit() for i in range(3)):
+ raise ValueError(f'unsupported size output for {path}')
+ sections = dict(zip(('text', 'data', 'bss'), map(int, rows[1][:3])))
+ needed = sorted(NEEDED.findall(run('readelf', '-d', path)))
+ return {'sections': sections, 'needed': needed,
+ 'sha256': hashlib.sha256(path.read_bytes()).hexdigest()}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--build', type=Path, required=True)
+ parser.add_argument('--budget', type=Path, default=ROOT / 'profile-budget.json')
+ parser.add_argument('--only', action='append', metavar='BINARY',
+ help='check only this selected binary (repeatable)')
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args()
+ if args.output and args.output.exists():
+ parser.error('evidence file already exists')
+ budget = json.loads(args.budget.read_text())
+ selected = args.only or list(budget['binaries'])
+ unknown = set(selected) - set(budget['binaries'])
+ if unknown:
+ parser.error(f'unknown budget binary: {sorted(unknown)}')
+ binaries = {}
+ failures = []
+ for name in selected:
+ limits = budget['binaries'][name]
+ path = args.build / name
+ try:
+ observed = inspect(path)
+ except (OSError, subprocess.CalledProcessError, ValueError) as error:
+ failures.append(f'{name}: cannot inspect: {error}')
+ continue
+ binaries[name] = observed
+ for section, value in observed['sections'].items():
+ limit = limits[f'max_{section}_bytes']
+ if value > limit:
+ failures.append(f'{name} {section}: {value} > {limit}')
+ if observed['needed'] != sorted(limits['needed']):
+ failures.append(f'{name} dependencies: {observed["needed"]} '
+ f'!= {sorted(limits["needed"])}')
+ result = {'passed': not failures, 'failures': failures,
+ 'profile': budget['profile'], 'scope': budget['scope'],
+ 'budget_sha256': hashlib.sha256(args.budget.read_bytes()).hexdigest(),
+ 'platform': platform.platform(), 'binaries': binaries}
+ output = json.dumps(result, indent=2) + '\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ with args.output.open('x') as destination:
+ destination.write(output)
+ print(output, end='')
+ if failures:
+ raise SystemExit(1)
+
+
+if __name__ == '__main__':
+ main()