diff --git a/profiling/harness.py b/profiling/harness.py index b469d20fe..40a9592b9 100644 --- a/profiling/harness.py +++ b/profiling/harness.py @@ -1,8 +1,13 @@ # profiling/harness.py from __future__ import annotations +import json +import subprocess import time from dataclasses import dataclass +from datetime import UTC +from datetime import datetime +from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Generic @@ -17,6 +22,8 @@ if TYPE_CHECKING: from django.db.models import QuerySet + from profiling.seed import ScaleProfile + T = TypeVar("T") @@ -65,3 +72,52 @@ def capture_explain_analyze(queryset: QuerySet[Any]) -> str: with connection.cursor() as cur: cur.execute(f"EXPLAIN ANALYZE {sql}", params) return "\n".join(row[0] for row in cur.fetchall()) + + +def _current_git_ref() -> str: + result = subprocess.run( + ["git", "rev-parse", "--short", "HEAD"], + capture_output=True, + text=True, + check=False, + ) + return result.stdout.strip() or "unknown" + + +def append_profiling_history( + *, + stage: str, + scenario: str, + best_seconds: float, + query_count: int, + scale: ScaleProfile, + code_ref: str | None = None, +) -> None: + """ + Append one line to profiling/results/history.jsonl -- an append-only, + cross-session record of every profiling run. Unlike the per-stage + before/after snapshot JSON files (which the next run of the same stage + overwrites), this never gets clobbered, so a profiling effort picked + back up days later has a full timeline to resume from instead of only + the single most recent comparison. + + ``code_ref`` should identify the *production* code under test (a short + SHA on the feature branch, not this `profiling` branch) -- pass it + explicitly when known. Left as ``None``, it falls back to this repo's + current ``git rev-parse --short HEAD``, which is only meaningful if + you're actually running from a checkout where that reflects the code + being measured. + """ + results_dir = Path(__file__).parent / "results" + results_dir.mkdir(exist_ok=True) + entry = { + "timestamp": datetime.now(UTC).isoformat(), + "code_ref": code_ref or _current_git_ref(), + "stage": stage, + "scenario": scenario, + "scale": scale, + "best_seconds": best_seconds, + "query_count": query_count, + } + with (results_dir / "history.jsonl").open("a") as f: + f.write(json.dumps(entry) + "\n") diff --git a/profiling/test_harness_self_check.py b/profiling/test_harness_self_check.py index 81c314f77..0bb21a418 100644 --- a/profiling/test_harness_self_check.py +++ b/profiling/test_harness_self_check.py @@ -1,8 +1,12 @@ # profiling/test_harness_self_check.py from __future__ import annotations +import json +from pathlib import Path + import pytest +from profiling.harness import append_profiling_history from profiling.harness import require_postgres from profiling.harness import run_profile from profiling.seed import seed_permission_dataset @@ -44,3 +48,27 @@ def test_run_profile_reports_query_count_and_timing() -> None: assert result.best_seconds >= 0 assert len(result.all_seconds) == 3 assert result.query_count >= 1 + + +def test_append_profiling_history_writes_a_valid_json_line() -> None: + history_path = Path(__file__).parent / "results" / "history.jsonl" + before_lines = ( + history_path.read_text().splitlines() if history_path.exists() else [] + ) + + append_profiling_history( + stage="self-check", + scenario="test_append_profiling_history_writes_a_valid_json_line", + best_seconds=0.001, + query_count=0, + scale="medium", + code_ref="test-run", + ) + + after_lines = history_path.read_text().splitlines() + assert len(after_lines) == len(before_lines) + 1 + entry = json.loads(after_lines[-1]) + assert entry["stage"] == "self-check" + assert entry["code_ref"] == "test-run" + assert entry["scale"] == "medium" + assert "timestamp" in entry