vergabe-teilnahme/scripts/compare_synthetic_limits.py
tegwick 44e5458b17
All checks were successful
Application acceptance / application-tests (push) Successful in 10s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
feat: configure synthetic operation mix and measure burst restart recovery
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 15:36:17 +02:00

84 lines
3.4 KiB
Python

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))