-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsummarize_results.py
More file actions
95 lines (82 loc) · 3.61 KB
/
Copy pathsummarize_results.py
File metadata and controls
95 lines (82 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
"""Summarize reproducible AI API observations from JSONL without dependencies."""
from __future__ import annotations
import argparse
import json
import math
from collections import Counter
from pathlib import Path
from typing import Any
REQUIRED = {"timestamp", "model_id", "test_region", "network", "status", "elapsed_ms", "request_feature"}
def percentile(values: list[float], q: float) -> float | None:
if not values:
return None
ordered = sorted(values)
position = (len(ordered) - 1) * q
lower = math.floor(position)
upper = math.ceil(position)
if lower == upper:
return round(ordered[lower], 2)
result = ordered[lower] + (ordered[upper] - ordered[lower]) * (position - lower)
return round(result, 2)
def load_jsonl(path: Path) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
try:
row = json.loads(line)
except json.JSONDecodeError as exc:
raise ValueError(f"line {line_number}: invalid JSON ({exc.msg})") from exc
missing = REQUIRED - row.keys()
if missing:
raise ValueError(f"line {line_number}: missing {', '.join(sorted(missing))}")
if not isinstance(row["status"], int) or not 100 <= row["status"] <= 599:
raise ValueError(f"line {line_number}: status must be an HTTP status integer")
if not isinstance(row["elapsed_ms"], (int, float)) or row["elapsed_ms"] < 0:
raise ValueError(f"line {line_number}: elapsed_ms must be non-negative")
rows.append(row)
if not rows:
raise ValueError("input contains no observations")
return rows
def summarize(rows: list[dict[str, Any]]) -> dict[str, Any]:
elapsed = [float(row["elapsed_ms"]) for row in rows]
statuses = Counter(str(row["status"]) for row in rows)
success = sum(1 for row in rows if 200 <= row["status"] < 300)
dimensions = {
name: sorted({str(row[name]) for row in rows})
for name in ("model_id", "test_region", "network", "request_feature")
}
return {
"schema_version": 1,
"sample_count": len(rows),
"success_count": success,
"success_rate": round(success / len(rows), 4),
"p50_ms": percentile(elapsed, 0.50),
"p95_ms": percentile(elapsed, 0.95),
"min_ms": round(min(elapsed), 2),
"max_ms": round(max(elapsed), 2),
"http_status_distribution": dict(sorted(statuses.items())),
"first_timestamp": min(str(row["timestamp"]) for row in rows),
"last_timestamp": max(str(row["timestamp"]) for row in rows),
**dimensions,
"method": "Linear interpolation on observed end-to-end elapsed_ms; success means HTTP 2xx.",
"disclaimer": "This summary describes only the supplied samples and is not a provider SLA or long-term availability guarantee."
}
def main() -> int:
parser = argparse.ArgumentParser(description="Summarize AI API JSONL observations.")
parser.add_argument("input", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
try:
report = summarize(load_jsonl(args.input))
except (OSError, ValueError) as exc:
parser.error(str(exc))
rendered = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(rendered, encoding="utf-8")
print(rendered, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())