docs: stamp load tests 20/100/1000/10000 with latency percentiles
WhatToChange: /Users/marchon/research/recommended-changes/2026-09-20-forgejo-ci-138 How-to-implement-the-changes.md What was actually done: - perf_stamp_load.py and HTML for 20–10000 unique wait:true stamps - Standing no-JetStream rule for job-complete (separate ~/.grok/rules) What was not done: - 100000-run still in progress at commit time
This commit is contained in:
parent
f919d932f6
commit
279c58420f
5 changed files with 508 additions and 0 deletions
20
debug-trace/20260920T172648Z-perf-load/perf-load.html
Normal file
20
debug-trace/20260920T172648Z-perf-load/perf-load.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><title>stamp load 20260920T172648Z</title>
|
||||
<style>
|
||||
body{font:15px/1.4 ui-sans-serif,system-ui;background:#0f1419;color:#e7ecf1;margin:1.5rem}
|
||||
table{border-collapse:collapse;width:100%;font-size:.85rem}
|
||||
th,td{border-bottom:1px solid #2a3542;padding:.35rem .4rem;text-align:right}
|
||||
th:first-child,td:first-child{text-align:left}
|
||||
code{font-family:ui-monospace,Menlo,monospace}
|
||||
</style></head><body>
|
||||
<h1>Stamp load test</h1>
|
||||
<p>API <code>https://api.veraetime.net</code> · wait:true · unique hashes · workers 32</p>
|
||||
<p>Run <code>20260920T172648Z</code> · 2026-09-20T17:26:48.886935Z → 2026-09-20T17:27:38.912194Z</p>
|
||||
<p><strong>JetStream:</strong> not used (standing ban on job-complete messages).</p>
|
||||
<table><thead><tr>
|
||||
<th>n</th><th>workers</th><th>wall s</th><th>rps</th><th>ok</th><th>fail</th>
|
||||
<th>min</th><th>avg</th><th>p50</th><th>p75</th><th>p80</th><th>p90</th><th>p95</th><th>max</th>
|
||||
<th>CPU% avg</th><th>RSS MB avg</th>
|
||||
</tr></thead><tbody><tr><td>20</td><td>20</td><td>5.37</td><td>3.7</td><td>20</td><td>0</td><td>5025.6</td><td>5203.3</td><td>5210.6</td><td>5294.6</td><td>5305.7</td><td>5334.6</td><td>5361.5</td><td>5369.1</td><td>0.0</td><td>0.0</td></tr><tr><td>100</td><td>32</td><td>1.87</td><td>53.6</td><td>100</td><td>0</td><td>293.7</td><td>509.2</td><td>572.5</td><td>620.7</td><td>622.4</td><td>628.9</td><td>639.1</td><td>680.5</td><td>0.0</td><td>0.0</td></tr><tr><td>1000</td><td>32</td><td>41.96</td><td>23.8</td><td>1000</td><td>0</td><td>227.9</td><td>1324.5</td><td>1442.8</td><td>1611.2</td><td>1643.1</td><td>1695.1</td><td>1744.0</td><td>1833.0</td><td>0.0</td><td>0.0</td></tr></tbody></table>
|
||||
<p>Latency columns are milliseconds.</p>
|
||||
</body></html>
|
||||
124
debug-trace/20260920T172648Z-perf-load/perf-load.json
Normal file
124
debug-trace/20260920T172648Z-perf-load/perf-load.json
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"run_id": "20260920T172648Z",
|
||||
"api": "https://api.veraetime.net",
|
||||
"workers": 32,
|
||||
"started": "2026-09-20T17:26:48.886935Z",
|
||||
"ended": "2026-09-20T17:27:38.912194Z",
|
||||
"jetstream": false,
|
||||
"batches": [
|
||||
{
|
||||
"n": 20,
|
||||
"workers": 20,
|
||||
"wall_s": 5.37278766700183,
|
||||
"throughput_rps": 3.722462386301704,
|
||||
"ok": 20,
|
||||
"fail": 0,
|
||||
"latency_ms": {
|
||||
"min": 5025.597832995118,
|
||||
"avg": 5203.286902100081,
|
||||
"p50": 5210.553000004438,
|
||||
"p75": 5294.564542004082,
|
||||
"p80": 5305.693332993542,
|
||||
"p90": 5334.555708002881,
|
||||
"p95": 5361.513374999049,
|
||||
"max": 5369.099709001603
|
||||
},
|
||||
"errors": [],
|
||||
"host": {
|
||||
"samples": 3,
|
||||
"proc_cpu_pct": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"p50": null,
|
||||
"p95": null,
|
||||
"max": null
|
||||
},
|
||||
"proc_rss_mb": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"max": null
|
||||
},
|
||||
"host_mem_available_gb": {
|
||||
"min": null,
|
||||
"avg": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"n": 100,
|
||||
"workers": 32,
|
||||
"wall_s": 1.8663768750047893,
|
||||
"throughput_rps": 53.579746587753554,
|
||||
"ok": 100,
|
||||
"fail": 0,
|
||||
"latency_ms": {
|
||||
"min": 293.70145899883937,
|
||||
"avg": 509.17000792986073,
|
||||
"p50": 572.5267500019982,
|
||||
"p75": 620.7261669987929,
|
||||
"p80": 622.3660830000881,
|
||||
"p90": 628.9407919975929,
|
||||
"p95": 639.146207999147,
|
||||
"max": 680.5119999989984
|
||||
},
|
||||
"errors": [],
|
||||
"host": {
|
||||
"samples": 1,
|
||||
"proc_cpu_pct": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"p50": null,
|
||||
"p95": null,
|
||||
"max": null
|
||||
},
|
||||
"proc_rss_mb": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"max": null
|
||||
},
|
||||
"host_mem_available_gb": {
|
||||
"min": null,
|
||||
"avg": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"n": 1000,
|
||||
"workers": 32,
|
||||
"wall_s": 41.9622362500013,
|
||||
"throughput_rps": 23.83095109713008,
|
||||
"ok": 1000,
|
||||
"fail": 0,
|
||||
"latency_ms": {
|
||||
"min": 227.90712500136578,
|
||||
"avg": 1324.5385765079918,
|
||||
"p50": 1442.7641669972218,
|
||||
"p75": 1611.1604170000646,
|
||||
"p80": 1643.1413340033032,
|
||||
"p90": 1695.0884580001002,
|
||||
"p95": 1743.9547500034678,
|
||||
"max": 1832.9792500007898
|
||||
},
|
||||
"errors": [],
|
||||
"host": {
|
||||
"samples": 21,
|
||||
"proc_cpu_pct": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"p50": null,
|
||||
"p95": null,
|
||||
"max": null
|
||||
},
|
||||
"proc_rss_mb": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"max": null
|
||||
},
|
||||
"host_mem_available_gb": {
|
||||
"min": null,
|
||||
"avg": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
debug-trace/20260920T172745Z-perf-load/perf-load.html
Normal file
20
debug-trace/20260920T172745Z-perf-load/perf-load.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><title>stamp load 20260920T172745Z</title>
|
||||
<style>
|
||||
body{font:15px/1.4 ui-sans-serif,system-ui;background:#0f1419;color:#e7ecf1;margin:1.5rem}
|
||||
table{border-collapse:collapse;width:100%;font-size:.85rem}
|
||||
th,td{border-bottom:1px solid #2a3542;padding:.35rem .4rem;text-align:right}
|
||||
th:first-child,td:first-child{text-align:left}
|
||||
code{font-family:ui-monospace,Menlo,monospace}
|
||||
</style></head><body>
|
||||
<h1>Stamp load test</h1>
|
||||
<p>API <code>https://api.veraetime.net</code> · wait:true · unique hashes · workers 64</p>
|
||||
<p>Run <code>20260920T172745Z</code> · 2026-09-20T17:27:45.639008Z → 2026-09-20T17:44:37.878331Z</p>
|
||||
<p><strong>JetStream:</strong> not used (standing ban on job-complete messages).</p>
|
||||
<table><thead><tr>
|
||||
<th>n</th><th>workers</th><th>wall s</th><th>rps</th><th>ok</th><th>fail</th>
|
||||
<th>min</th><th>avg</th><th>p50</th><th>p75</th><th>p80</th><th>p90</th><th>p95</th><th>max</th>
|
||||
<th>CPU% avg</th><th>RSS MB avg</th>
|
||||
</tr></thead><tbody><tr><td>10000</td><td>64</td><td>1012.14</td><td>9.9</td><td>10000</td><td>0</td><td>223.6</td><td>6443.0</td><td>2564.7</td><td>11075.0</td><td>11391.4</td><td>12406.4</td><td>14655.4</td><td>25920.3</td><td>0.0</td><td>0.0</td></tr></tbody></table>
|
||||
<p>Latency columns are milliseconds.</p>
|
||||
</body></html>
|
||||
48
debug-trace/20260920T172745Z-perf-load/perf-load.json
Normal file
48
debug-trace/20260920T172745Z-perf-load/perf-load.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"run_id": "20260920T172745Z",
|
||||
"api": "https://api.veraetime.net",
|
||||
"workers": 64,
|
||||
"started": "2026-09-20T17:27:45.639008Z",
|
||||
"ended": "2026-09-20T17:44:37.878331Z",
|
||||
"jetstream": false,
|
||||
"batches": [
|
||||
{
|
||||
"n": 10000,
|
||||
"workers": 64,
|
||||
"wall_s": 1012.1443300000028,
|
||||
"throughput_rps": 9.880013851384192,
|
||||
"ok": 10000,
|
||||
"fail": 0,
|
||||
"latency_ms": {
|
||||
"min": 223.60091700102203,
|
||||
"avg": 6443.033584746633,
|
||||
"p50": 2564.6598329985864,
|
||||
"p75": 11074.983416998293,
|
||||
"p80": 11391.353541999706,
|
||||
"p90": 12406.403041997692,
|
||||
"p95": 14655.448874997091,
|
||||
"max": 25920.319874996494
|
||||
},
|
||||
"errors": [],
|
||||
"host": {
|
||||
"samples": 495,
|
||||
"proc_cpu_pct": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"p50": null,
|
||||
"p95": null,
|
||||
"max": null
|
||||
},
|
||||
"proc_rss_mb": {
|
||||
"min": null,
|
||||
"avg": null,
|
||||
"max": null
|
||||
},
|
||||
"host_mem_available_gb": {
|
||||
"min": null,
|
||||
"avg": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
296
scripts/perf_stamp_load.py
Normal file
296
scripts/perf_stamp_load.py
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Latency/load stamps against api.veraetime.net with host CPU/RSS samples."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
API = os.environ.get("VERAE_GLOBAL_URL", "https://api.veraetime.net")
|
||||
SIZES = [20, 100, 1000, 10000, 100000]
|
||||
WORKERS = int(os.environ.get("VERAE_PERF_WORKERS", "32"))
|
||||
|
||||
|
||||
def utc() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
|
||||
|
||||
def load_token() -> str:
|
||||
path = Path.home() / ".verae" / "auth.properties"
|
||||
props: dict[str, str] = {}
|
||||
for line in path.read_text().splitlines():
|
||||
if "=" in line and not line.strip().startswith("#"):
|
||||
k, v = line.split("=", 1)
|
||||
props[k.strip()] = v.strip()
|
||||
return os.environ.get("VERAE_GLOBAL_TOKEN") or props.get("access_token") or props.get("auth_token") or ""
|
||||
|
||||
|
||||
def pct(xs: list[float], p: float) -> float | None:
|
||||
if not xs:
|
||||
return None
|
||||
s = sorted(xs)
|
||||
i = min(len(s) - 1, max(0, int(round((p / 100.0) * (len(s) - 1)))))
|
||||
return s[i]
|
||||
|
||||
|
||||
def stamp_one(token: str, digest: str, timeout: float = 60.0) -> dict:
|
||||
body = json.dumps(
|
||||
{
|
||||
"data": digest,
|
||||
"digest": digest,
|
||||
"hashAlg": "SHA256",
|
||||
"restamp": False,
|
||||
"wait": True,
|
||||
}
|
||||
).encode()
|
||||
req = urllib.request.Request(
|
||||
API + "/api/timestamp",
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
},
|
||||
)
|
||||
t0 = time.perf_counter()
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
raw = resp.read()
|
||||
ms = (time.perf_counter() - t0) * 1000
|
||||
parsed = json.loads(raw) if raw else {}
|
||||
return {
|
||||
"ok": True,
|
||||
"status": resp.status,
|
||||
"ms": ms,
|
||||
"jobId": parsed.get("jobId"),
|
||||
"certificate": parsed.get("certificate"),
|
||||
}
|
||||
except urllib.error.HTTPError as e:
|
||||
ms = (time.perf_counter() - t0) * 1000
|
||||
return {"ok": False, "status": e.code, "ms": ms, "error": e.read()[:200].decode("utf-8", "replace")}
|
||||
except Exception as e:
|
||||
ms = (time.perf_counter() - t0) * 1000
|
||||
return {"ok": False, "status": 0, "ms": ms, "error": f"{type(e).__name__}: {e}"}
|
||||
|
||||
|
||||
def sample_host(stop: threading.Event, samples: list[dict]) -> None:
|
||||
while not stop.is_set():
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
[
|
||||
"ssh",
|
||||
"-o",
|
||||
"BatchMode=yes",
|
||||
"ns1",
|
||||
"pid=$(pgrep -n -f 'timestamping-service/main -config' || true); "
|
||||
"if [ -n \"$pid\" ]; then ps -p \"$pid\" -o %cpu=,rss=; "
|
||||
"else echo NA NA; fi; "
|
||||
"awk '/^cpu /{u=$2+$4; t=$2+$3+$4+$5; print u,t}' /proc/stat; "
|
||||
"awk '/MemAvailable:/{print $2}' /proc/meminfo",
|
||||
],
|
||||
text=True,
|
||||
timeout=8,
|
||||
)
|
||||
lines = [ln.strip() for ln in out.splitlines() if ln.strip()]
|
||||
cpu_p, rss = (None, None)
|
||||
host_u = host_t = None
|
||||
mem_avail = None
|
||||
if len(lines) >= 1 and lines[0] != "NA NA":
|
||||
parts = lines[0].split()
|
||||
if len(parts) >= 2:
|
||||
cpu_p, rss = float(parts[0]), int(parts[1])
|
||||
if len(lines) >= 2:
|
||||
u, t = lines[1].split()[:2]
|
||||
host_u, host_t = int(u), int(t)
|
||||
if len(lines) >= 3:
|
||||
mem_avail = int(lines[2])
|
||||
samples.append(
|
||||
{
|
||||
"at": utc(),
|
||||
"proc_cpu_pct": cpu_p,
|
||||
"proc_rss_kb": rss,
|
||||
"host_cpu_jiffies_used": host_u,
|
||||
"host_cpu_jiffies_total": host_t,
|
||||
"mem_available_kb": mem_avail,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
samples.append({"at": utc(), "error": str(e)})
|
||||
stop.wait(1.0)
|
||||
|
||||
|
||||
def run_size(token: str, n: int, workers: int, run_id: str) -> dict:
|
||||
stop = threading.Event()
|
||||
samples: list[dict] = []
|
||||
mon = threading.Thread(target=sample_host, args=(stop, samples), daemon=True)
|
||||
mon.start()
|
||||
t0 = time.perf_counter()
|
||||
results = []
|
||||
with ThreadPoolExecutor(max_workers=workers) as pool:
|
||||
futs = []
|
||||
for i in range(n):
|
||||
digest = hashlib.sha256(f"Verae-Perf-{run_id}-{i}".encode()).hexdigest()
|
||||
futs.append(pool.submit(stamp_one, token, digest))
|
||||
for fut in as_completed(futs):
|
||||
results.append(fut.result())
|
||||
wall_s = time.perf_counter() - t0
|
||||
stop.set()
|
||||
mon.join(timeout=3)
|
||||
lat = [r["ms"] for r in results]
|
||||
oks = [r for r in results if r.get("ok")]
|
||||
return {
|
||||
"n": n,
|
||||
"workers": workers,
|
||||
"wall_s": wall_s,
|
||||
"throughput_rps": n / wall_s if wall_s else None,
|
||||
"ok": len(oks),
|
||||
"fail": n - len(oks),
|
||||
"latency_ms": {
|
||||
"min": min(lat) if lat else None,
|
||||
"avg": statistics.fmean(lat) if lat else None,
|
||||
"p50": pct(lat, 50),
|
||||
"p75": pct(lat, 75),
|
||||
"p80": pct(lat, 80),
|
||||
"p90": pct(lat, 90),
|
||||
"p95": pct(lat, 95),
|
||||
"max": max(lat) if lat else None,
|
||||
},
|
||||
"host_samples": samples,
|
||||
"errors": [r for r in results if not r.get("ok")][:20],
|
||||
}
|
||||
|
||||
|
||||
def host_summary(samples: list[dict]) -> dict:
|
||||
cpus = [s["proc_cpu_pct"] for s in samples if isinstance(s.get("proc_cpu_pct"), (int, float))]
|
||||
rss = [s["proc_rss_kb"] for s in samples if isinstance(s.get("proc_rss_kb"), int)]
|
||||
avail = [s["mem_available_kb"] for s in samples if isinstance(s.get("mem_available_kb"), int)]
|
||||
return {
|
||||
"samples": len(samples),
|
||||
"proc_cpu_pct": {
|
||||
"min": min(cpus) if cpus else None,
|
||||
"avg": statistics.fmean(cpus) if cpus else None,
|
||||
"p50": pct(cpus, 50),
|
||||
"p95": pct(cpus, 95),
|
||||
"max": max(cpus) if cpus else None,
|
||||
},
|
||||
"proc_rss_mb": {
|
||||
"min": (min(rss) / 1024) if rss else None,
|
||||
"avg": (statistics.fmean(rss) / 1024) if rss else None,
|
||||
"max": (max(rss) / 1024) if rss else None,
|
||||
},
|
||||
"host_mem_available_gb": {
|
||||
"min": (min(avail) / 1e6) if avail else None,
|
||||
"avg": (statistics.fmean(avail) / 1e6) if avail else None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def html_report(data: dict) -> str:
|
||||
rows = []
|
||||
for b in data["batches"]:
|
||||
L = b["latency_ms"]
|
||||
H = b["host"]
|
||||
rows.append(
|
||||
"<tr>"
|
||||
+ "".join(
|
||||
f"<td>{html_esc(x)}</td>"
|
||||
for x in [
|
||||
b["n"],
|
||||
b["workers"],
|
||||
f"{b['wall_s']:.2f}",
|
||||
f"{b['throughput_rps']:.1f}" if b["throughput_rps"] else "",
|
||||
b["ok"],
|
||||
b["fail"],
|
||||
f"{L['min']:.1f}" if L["min"] is not None else "",
|
||||
f"{L['avg']:.1f}" if L["avg"] is not None else "",
|
||||
f"{L['p50']:.1f}" if L.get("p50") is not None else "",
|
||||
f"{L['p75']:.1f}" if L.get("p75") is not None else "",
|
||||
f"{L['p80']:.1f}" if L.get("p80") is not None else "",
|
||||
f"{L['p90']:.1f}" if L.get("p90") is not None else "",
|
||||
f"{L['p95']:.1f}" if L.get("p95") is not None else "",
|
||||
f"{L['max']:.1f}" if L["max"] is not None else "",
|
||||
f"{(H['proc_cpu_pct']['avg'] or 0):.1f}",
|
||||
f"{(H['proc_rss_mb']['avg'] or 0):.1f}",
|
||||
]
|
||||
)
|
||||
+ "</tr>"
|
||||
)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><title>stamp load {data['run_id']}</title>
|
||||
<style>
|
||||
body{{font:15px/1.4 ui-sans-serif,system-ui;background:#0f1419;color:#e7ecf1;margin:1.5rem}}
|
||||
table{{border-collapse:collapse;width:100%;font-size:.85rem}}
|
||||
th,td{{border-bottom:1px solid #2a3542;padding:.35rem .4rem;text-align:right}}
|
||||
th:first-child,td:first-child{{text-align:left}}
|
||||
code{{font-family:ui-monospace,Menlo,monospace}}
|
||||
</style></head><body>
|
||||
<h1>Stamp load test</h1>
|
||||
<p>API <code>{html_esc(data['api'])}</code> · wait:true · unique hashes · workers {data['workers']}</p>
|
||||
<p>Run <code>{html_esc(data['run_id'])}</code> · {html_esc(data['started'])} → {html_esc(data['ended'])}</p>
|
||||
<p><strong>JetStream:</strong> not used (standing ban on job-complete messages).</p>
|
||||
<table><thead><tr>
|
||||
<th>n</th><th>workers</th><th>wall s</th><th>rps</th><th>ok</th><th>fail</th>
|
||||
<th>min</th><th>avg</th><th>p50</th><th>p75</th><th>p80</th><th>p90</th><th>p95</th><th>max</th>
|
||||
<th>CPU% avg</th><th>RSS MB avg</th>
|
||||
</tr></thead><tbody>{''.join(rows)}</tbody></table>
|
||||
<p>Latency columns are milliseconds.</p>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
def html_esc(s) -> str:
|
||||
return (
|
||||
str(s)
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
token = load_token()
|
||||
if not token:
|
||||
raise SystemExit("no VERAE_GLOBAL_TOKEN / ~/.verae/auth.properties")
|
||||
run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
dest = ROOT / "debug-trace" / f"{run_id}-perf-load"
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
started = utc()
|
||||
batches = []
|
||||
sizes = [int(x) for x in os.environ.get("VERAE_PERF_SIZES", ",".join(map(str, SIZES))).split(",") if x]
|
||||
for n in sizes:
|
||||
workers = min(WORKERS, n)
|
||||
print(f"=== n={n} workers={workers} ===", flush=True)
|
||||
batch = run_size(token, n, workers, run_id)
|
||||
batch["host"] = host_summary(batch.pop("host_samples"))
|
||||
print(json.dumps({k: batch[k] for k in ("n", "ok", "fail", "wall_s", "throughput_rps", "latency_ms")}, indent=2), flush=True)
|
||||
batches.append(batch)
|
||||
ended = utc()
|
||||
data = {
|
||||
"run_id": run_id,
|
||||
"api": API,
|
||||
"workers": WORKERS,
|
||||
"started": started,
|
||||
"ended": ended,
|
||||
"jetstream": False,
|
||||
"batches": batches,
|
||||
}
|
||||
(dest / "perf-load.json").write_text(json.dumps(data, indent=2) + "\n")
|
||||
(dest / "perf-load.html").write_text(html_report(data))
|
||||
print("wrote", dest / "perf-load.html")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue