import argparse import json import math from pathlib import Path parser = argparse.ArgumentParser( description="Compare measured application cgroup limits; never apply resources" ) parser.add_argument("input", type=Path) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--expected-cpu-m", type=int, default=1000) parser.add_argument("--expected-memory-mib", type=int, default=1024) args = parser.parse_args() v = json.loads(args.input.read_text()) if not v.get("measurement_complete") or v.get("measurement_gaps"): raise ValueError("measurement gaps prevent a limit comparison") if v["app_runtime"]["nano_cpus"] != args.expected_cpu_m * 1000000: raise ValueError("actual CPU limit does not match expected limit") if v["app_runtime"]["memory_bytes"] != args.expected_memory_mib * 1024**2: raise ValueError("actual memory limit does not match expected limit") load_names = {p["name"] for p in v.get("phase_results", []) if p["kind"] == "load"} or {"ordinary"} samples = [s for s in v["runtime_samples"] if s.get("phase") in load_names and "cpu_seconds" in s] pairs = [ (a, b) for a, b in zip(samples, samples[1:]) if a["phase"] == b["phase"] and b["cpu_seconds"] >= a["cpu_seconds"] and b["monotonic_seconds"] > a["monotonic_seconds"] ] if not pairs: raise ValueError("no valid CPU sample intervals") rates = [ (b["cpu_seconds"] - a["cpu_seconds"]) / (b["monotonic_seconds"] - a["monotonic_seconds"]) * 1000 for a, b in pairs ] def p95(values): return sorted(values)[math.ceil(0.95 * len(values)) - 1] def counter_delta(key): return sum(max(0, b["cpu_stat"][key] - a["cpu_stat"][key]) for a, b in pairs) db = [s["docker_stats"] for s in v["database_samples"] if s["phase"] in load_names] report = { "status": "provisional-existing-limit-comparison", "ordinary": v["ordinary"], "startup_seconds": v.get("startup_seconds"), "phase_results": v.get("phase_results", []), "document_transfer": v["document_transfer"], "complete": v["complete"], "provisional_pass": v["provisional_pass"], "measurement_complete": v["measurement_complete"], "measurement_gaps": v["measurement_gaps"], "config": v["config"], "completed_iterations": v["completed_iterations"], "app_runtime": v["app_runtime"], "application_measured": { "sample_intervals": len(rates), "cpu_p95_m": p95(rates), "cpu_mean_m": sum(b["cpu_seconds"] - a["cpu_seconds"] for a, b in pairs) / sum(b["monotonic_seconds"] - a["monotonic_seconds"] for a, b in pairs) * 1000, "cgroup_memory_peak_mib": max(s["rss_bytes"] for s in samples) / 1024**2, "throttled_periods": counter_delta("nr_throttled"), "periods": counter_delta("nr_periods"), "throttled_usec": counter_delta("throttled_usec"), }, "database_separate": { "samples": len(db), "cpu_p95_percent": p95([float(s["CPUPerc"].rstrip("%")) for s in db]), "memory_snapshots_kind": "docker usage; full intervals retained raw", }, "interpretation": ( "No production limit increase implied. The inspected limit is tested only for this " "local synthetic mix. 60m request is not validated as adequate under contention. " "Memory includes page cache; comparison to earlier summedRSS is not equivalent. " "No human/ingress/SSO acceptance." ), } args.output.write_text(json.dumps(report, indent=2) + "\n") print(json.dumps(report, indent=2))