Viewing files at commit 186ae84b74bdCommit detailsView latest

Browse files
scripts/oakbench/shallow_refresh.py 179 lines · 6.8 KB · python
1
"""Oracle and metric helpers for ``shallow_worker_branch_refresh_v1``.
2
3
The scenario asks: starting from a fresh shallow worker, create a named task
4
branch at the server's current ``main`` and perform the first parent refresh.
5
Success is judged from durable on-disk state and from the server, never from
6
the measured command's own output:
7
8
- Oak: ``.oak/oak.db`` is read with SQLite in query-only mode (current
9
  branch, branch head, stored commit rows, ancestry closure).
10
- Git: ``git rev-parse`` / ``symbolic-ref`` in the worktree (stock Git is the
11
  known-good reader for Git state).
12
- Expected head comes from the server (Oak branch API / bare repo rev-parse);
13
  expected tree comes from the fixture model, not from any VCS.
14
"""
15
16
from __future__ import annotations
17
18
import sqlite3
19
import subprocess
20
from pathlib import Path
21
from typing import Any
22
23
from oakbench.byteproxy import ByteProxyStats
24
from oakbench.httpcount import RequestStats
25
from oakbench.merge_history_fixture import tree_content_hash, worktree_files
26
27
SERVE_DEPTH_IGNORED = "serve_depth_ignored"
28
SERVE_DEPTH_HONORED = "serve_depth_honored"
29
30
31
def oak_disk_state(root: Path, branch: str) -> dict[str, Any]:
32
    """Read Oak checkout state from ``.oak/oak.db``; unobserved fields are None."""
33
    state: dict[str, Any] = {
34
        "current_branch": None,
35
        "head": None,
36
        "branch_head": None,
37
        "commit_rows": None,
38
        "ancestry_missing_edges": None,
39
        "error": None,
40
    }
41
    db = root / ".oak" / "oak.db"
42
    if not db.is_file():
43
        state["error"] = "oak_db_missing"
44
        return state
45
    try:
46
        # Same access pattern as task_loop.stat_cache_snapshot: a WAL database
47
        # cannot be opened with mode=ro before its -shm exists.
48
        connection = sqlite3.connect(str(db))
49
    except sqlite3.Error as error:
50
        state["error"] = f"oak_db_open:{error}"
51
        return state
52
    try:
53
        connection.execute("PRAGMA query_only = ON")
54
        meta = dict(connection.execute("SELECT key, value FROM metadata").fetchall())
55
        state["current_branch"] = meta.get("current_branch")
56
        state["head"] = meta.get("head")
57
        row = connection.execute(
58
            "SELECT head_hash FROM branch_heads WHERE branch_name = ?", (branch,)
59
        ).fetchone()
60
        state["branch_head"] = row[0] if row else None
61
        edges = connection.execute(
62
            "SELECT hash, parent_hash, merge_parent_hash FROM commits"
63
        ).fetchall()
64
    except sqlite3.Error as error:
65
        state["error"] = f"oak_db_read:{error}"
66
        return state
67
    finally:
68
        connection.close()
69
    state["commit_rows"] = len(edges)
70
    state["ancestry_missing_edges"] = missing_ancestry_edges(edges, state["branch_head"])
71
    return state
72
73
74
def missing_ancestry_edges(
75
    edges: list[tuple[str, str | None, str | None]], head: str | None
76
) -> int | None:
77
    """Count parent edges reachable from ``head`` whose commit row is absent."""
78
    if head is None:
79
        return None
80
    parents = {commit: (parent, merge) for commit, parent, merge in edges}
81
    missing = 0
82
    stack = [head]
83
    seen: set[str] = set()
84
    while stack:
85
        commit = stack.pop()
86
        if commit in seen:
87
            continue
88
        seen.add(commit)
89
        if commit not in parents:
90
            missing += 1
91
            continue
92
        stack.extend(parent for parent in parents[commit] if parent)
93
    return missing
94
95
96
def git_disk_state(root: Path, git_bin: str, env: dict[str, str] | None = None) -> dict[str, Any]:
97
    def read(*args: str) -> str | None:
98
        try:
99
            result = subprocess.run(
100
                [git_bin, *args], cwd=root, env=env, text=True,
101
                stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False,
102
            )
103
        except (OSError, subprocess.TimeoutExpired):
104
            return None
105
        return result.stdout.strip() if result.returncode == 0 else None
106
107
    symbolic = read("symbolic-ref", "-q", "HEAD")
108
    commits = read("rev-list", "--count", "HEAD")
109
    return {
110
        "current_branch": symbolic.removeprefix("refs/heads/") if symbolic else None,
111
        "head": read("rev-parse", "HEAD"),
112
        "commit_rows": int(commits) if commits and commits.isdigit() else None,
113
        "shallow": read("rev-parse", "--is-shallow-repository"),
114
    }
115
116
117
def serve_depth_behavior(oak_commit_rows_after_clone: int | None) -> str | None:
118
    """Classify the served shallow clone from the rows it actually stored."""
119
    if oak_commit_rows_after_clone is None:
120
        return None
121
    return SERVE_DEPTH_HONORED if oak_commit_rows_after_clone <= 1 else SERVE_DEPTH_IGNORED
122
123
124
def evaluate_oracle(
125
    *,
126
    expected_branch: str,
127
    expected_head: str | None,
128
    expected_tree_hash: str,
129
    observed_branch: str | None,
130
    observed_branch_head: str | None,
131
    observed_head: str | None,
132
    worktree_tree_hash: str | None,
133
) -> dict[str, Any]:
134
    """Final-state oracle; an unobserved expected head can never pass."""
135
    checks = {
136
        "oracle_branch_current": observed_branch == expected_branch,
137
        "oracle_branch_head_is_server_main": bool(expected_head)
138
        and observed_branch_head == expected_head,
139
        "oracle_checkout_head_is_server_main": bool(expected_head) and observed_head == expected_head,
140
        "oracle_worktree_tree_matches_fixture": worktree_tree_hash == expected_tree_hash,
141
    }
142
    return {**checks, "oracle_passed": all(checks.values())}
143
144
145
def worktree_tree_hash(root: Path) -> str | None:
146
    if not root.is_dir():
147
        return None
148
    return tree_content_hash(worktree_files(root))
149
150
151
def request_metric_fields(
152
    stats: RequestStats | None, wire: ByteProxyStats | None, commits_info_suffix: str
153
) -> dict[str, Any]:
154
    """Per-step request/wire metrics; null (never 0) when unobserved."""
155
    return {
156
        "http_request_count": stats.request_count if stats else None,
157
        "http_commits_info_request_count": stats.count_suffix(commits_info_suffix) if stats else None,
158
        "http_requests_by_method": stats.methods if stats else None,
159
        "http_framing_errors": stats.framing_errors if stats else None,
160
        "injected_request_delay_ms": stats.injected_request_delay_ms if stats else None,
161
        "network_connections_observed": wire.connections if wire else None,
162
        "network_client_to_server_bytes_observed": wire.client_to_upstream_bytes if wire else None,
163
        "network_server_to_client_bytes_observed": wire.upstream_to_client_bytes if wire else None,
164
        "network_wire_bytes_observed": wire.total_bytes if wire else None,
165
        "request_measurement_source": "loopback_http1_request_framing_proxy" if stats else None,
166
    }
167
168
169
__all__ = [
170
    "SERVE_DEPTH_HONORED",
171
    "SERVE_DEPTH_IGNORED",
172
    "evaluate_oracle",
173
    "git_disk_state",
174
    "missing_ancestry_edges",
175
    "oak_disk_state",
176
    "request_metric_fields",
177
    "serve_depth_behavior",
178
    "worktree_tree_hash",
179
]