vergabe-teilnahme/scripts/summarize_synthetic_sizing.py
tegwick bfd1a2d46d feat: compare synthetic pilot at existing container limits with failure cleanup
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 15:15:00 +02:00

84 lines
3.7 KiB
Python

"""Derive a provisional local-host envelope, never apply deployment resources."""
import argparse
import json
import math
from pathlib import Path
def percentile(values):
values = sorted(values)
if not values:
raise ValueError("measurement samples missing")
return values[math.ceil(0.95 * len(values)) - 1]
def recommend(result, margin):
if result.get("measurement_gaps") or result.get("measurement_complete") is False:
raise ValueError("sampling gaps prevent a sizing recommendation")
if result.get("app_runtime"):
raise ValueError(
"quota-capped run requires direct limit comparison, not unbounded extrapolation"
)
samples = result["runtime_samples"]
cpu = []
memory = []
for before, after in zip(samples, samples[1:]):
if before.get("phase") != "ordinary" or after.get("phase") != "ordinary":
continue
if before.get("pids") != after.get("pids"):
continue
elapsed = after["monotonic_seconds"] - before["monotonic_seconds"]
used = after["cpu_seconds"] - before["cpu_seconds"]
if elapsed > 0 and used >= 0:
cpu.append(used / elapsed * 1000)
memory.append(after["rss_bytes"] / 1024**2)
if not cpu or not memory or not result["complete"] or not result["provisional_pass"]:
raise ValueError("incomplete or failed workload cannot establish a sizing envelope")
cpu_p95, cpu_peak, memory_peak = percentile(cpu), max(cpu), max(memory)
def rounded(value, quantum):
return math.ceil(value / quantum) * quantum
return {
"status": "provisional-local-host-only",
"application_observed": {
"cpu_p95_m": cpu_p95,
"cpu_peak_m": cpu_peak,
"rss_sum_peak_mib": memory_peak,
"sample_intervals": len(cpu),
"normalized_cpu_p95_m_per_worker": cpu_p95 / result["config"]["workers"],
},
"suggested_application": {
"cpu_request_m": rounded(cpu_p95 * margin["cpu_request_multiplier"], 25),
"cpu_limit_m": rounded(cpu_peak * margin["cpu_limit_multiplier"], 100),
"memory_request_mib": rounded(memory_peak * margin["memory_request_multiplier"], 64),
"memory_limit_mib": rounded(memory_peak * margin["memory_limit_multiplier"], 64),
},
"margins": margin,
"limits": [
"Per-worker number is normalized aggregate, not an independently sampled worker.",
"RSS sum double-counts shared pages; conservative upper bound, not unique memory.",
"Observed local CPU is hardware/think-time dependent; quota effects untested.",
"Database/container demand is separate and not included in application request.",
"No production apply or human acceptance; validate target runtime before right-sizing.",
],
"revisit": "Changed concurrency, corpus, document size, workers, image, hardware or quota; "
"then repeat under approved target runtime including bursts and restart.",
}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("result", type=Path)
parser.add_argument("--margins", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
margins = json.loads(args.margins.read_text())
if any(
not isinstance(v, (int, float)) or not math.isfinite(v) or v < 1 for v in margins.values()
):
raise ValueError("all margins must be finite and at least one")
args.output.write_text(
json.dumps(recommend(json.loads(args.result.read_text()), margins), indent=2) + "\n"
)