mirror of
https://github.com/vee1e/bulk-questionnaire-upload.git
synced 2026-09-01 09:50:06 +00:00
- update_readme_metrics.py: fall back to existing README values instead of overwriting with "N/A (MongoDB not connected)" when metrics are unavailable - measure_perf.py: fix uploaded_id extraction — ParsedForm.id is top-level, not nested under a "form" key, so delete timing now actually runs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Read perf_results.json and update the Performance Metrics Table in README.md.
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
from datetime import datetime, timezone
|
||
|
||
ROOT = os.path.join(os.path.dirname(__file__), "..")
|
||
RESULTS_FILE = os.path.join(ROOT, "perf_results.json")
|
||
README_FILE = os.path.join(ROOT, "README.md")
|
||
|
||
|
||
def fmt_range(m):
|
||
"""Format a min/max/avg dict into a readable string, or None if unavailable."""
|
||
if m is None:
|
||
return None
|
||
lo, hi = m["min_ms"], m["max_ms"]
|
||
return f"{lo:.0f}–{hi:.0f}ms ({lo/1000:.3f}–{hi/1000:.3f}s)"
|
||
|
||
|
||
def fmt_per(ms):
|
||
if ms is None:
|
||
return None
|
||
return f"{ms:.3f}ms per item"
|
||
|
||
|
||
def parse_existing_table(content):
|
||
"""Extract previous metric values from the README table by label."""
|
||
existing = {}
|
||
marker_pattern = re.compile(
|
||
r"<!-- PERF_TABLE_START -->.*?<!-- PERF_TABLE_END -->",
|
||
re.DOTALL,
|
||
)
|
||
match = marker_pattern.search(content)
|
||
if not match:
|
||
return existing
|
||
row_pattern = re.compile(r"\|\s*(\*\*[^|]+\*\*)\s*\|\s*([^|]+)\s*\|")
|
||
for m in row_pattern.finditer(match.group(0)):
|
||
label = m.group(1).strip()
|
||
value = m.group(2).strip()
|
||
existing[label] = value
|
||
return existing
|
||
|
||
|
||
def build_table(r, updated_at, existing=None):
|
||
v = r.get("validation")
|
||
p = r.get("parsing")
|
||
u = r.get("upload")
|
||
d = r.get("delete")
|
||
pq = r.get("per_question_ms")
|
||
po = r.get("per_option_ms")
|
||
cs = r.get("cold_start_ms")
|
||
|
||
cold_str = f"{cs:.0f}ms" if cs is not None else "N/A"
|
||
|
||
def keep_prev(label, value):
|
||
"""Return value if measured, otherwise the previous README value."""
|
||
if value is not None:
|
||
return value
|
||
if existing:
|
||
prev = existing.get(label, "")
|
||
if prev and "MongoDB not connected" not in prev and prev != "N/A":
|
||
return prev
|
||
return "N/A"
|
||
|
||
rows = [
|
||
("**File Validation**", keep_prev("**File Validation**", fmt_range(v))),
|
||
("**Form Parsing**", keep_prev("**Form Parsing**", fmt_range(p))),
|
||
("**Form Upload**", keep_prev("**Form Upload**", fmt_range(u))),
|
||
("**Question Processing**", keep_prev("**Question Processing**", fmt_per(pq))),
|
||
("**Option Processing**", keep_prev("**Option Processing**", fmt_per(po))),
|
||
("**Batch Processing**", keep_prev("**Batch Processing**", fmt_range(u))),
|
||
("**Delete Operations**", keep_prev("**Delete Operations**", fmt_range(d))),
|
||
("**Cold Start Time**", cold_str),
|
||
]
|
||
|
||
lines = [
|
||
f"<!-- PERF_TABLE_START -->",
|
||
f"",
|
||
f"*Last measured: {updated_at} UTC against the live Render deployment.*",
|
||
f"",
|
||
f"| Metric Type | Recent Performance |",
|
||
f"|-------------|-------------------|",
|
||
]
|
||
for label, value in rows:
|
||
lines.append(f"| {label} | {value} |")
|
||
lines.append("")
|
||
lines.append("<!-- PERF_TABLE_END -->")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def main():
|
||
if not os.path.exists(RESULTS_FILE):
|
||
print("perf_results.json not found – nothing to update.")
|
||
sys.exit(1)
|
||
|
||
with open(RESULTS_FILE) as f:
|
||
results = json.load(f)
|
||
|
||
updated_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
|
||
|
||
with open(README_FILE) as f:
|
||
content = f.read()
|
||
|
||
existing = parse_existing_table(content)
|
||
new_table = build_table(results, updated_at, existing=existing)
|
||
|
||
# Replace between markers if they exist, otherwise replace the whole table
|
||
marker_pattern = re.compile(
|
||
r"<!-- PERF_TABLE_START -->.*?<!-- PERF_TABLE_END -->",
|
||
re.DOTALL,
|
||
)
|
||
if marker_pattern.search(content):
|
||
updated = marker_pattern.sub(new_table, content)
|
||
else:
|
||
# First run: inject markers around the existing table
|
||
table_pattern = re.compile(
|
||
r"(\| Metric Type \| Recent Performance \|.*?\n(?:\|.*?\n)+)",
|
||
re.DOTALL,
|
||
)
|
||
if table_pattern.search(content):
|
||
updated = table_pattern.sub(new_table + "\n", content)
|
||
else:
|
||
print("Could not find metrics table in README – aborting.")
|
||
sys.exit(1)
|
||
|
||
with open(README_FILE, "w") as f:
|
||
f.write(updated)
|
||
|
||
print(f"README updated with metrics from {updated_at}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|