brick/tests/benchmarks/mcp_runner.py
trvon d292b9f3f2
feat: add brick_callers and brick_uncalled caller-status queries
Agents asking "is this function used?" get resolved, unknown, or
unreferenced per function instead of a bare zero in-degree. Unknown lists
why callers may exist: same-name unresolved call sites (excluding foreign
receivers and macros), name references (callbacks, address-taken, macro
bodies), virtual/trait-impl/external-base/outside-class members, framework
attributes, implicit protocol invocations, entry points, test code,
incomplete parses, and, unless assume_closed_world is set, exported or
external-linkage functions. Unreferenced is a review candidate with an
explicit heuristic/compiler evidence basis, never a dead-code proof.

The queries are read-only over validated schema-4 models and return
complete canonical nodes and edges plus metrics.resolution coverage.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 15:12:13 -06:00

371 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Artifact-first benchmark for Brick's read-only MCP query surface."""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
import pathlib
import platform
import shutil
import statistics
import subprocess
import sys
import time
import runner
ROOT = pathlib.Path(__file__).resolve().parents[2]
PLAN = ROOT / "tests" / "benchmarks" / "plans" / "mcp_queries.json"
PROTOCOL_VERSION = "2025-06-18"
TOOL_NAMES = [
"brick_impact",
"brick_cycles",
"brick_bridges",
"brick_callers",
"brick_uncalled",
"brick_evidence",
]
def load_plan() -> dict:
plan = json.loads(PLAN.read_text(encoding="utf-8"))
if plan.get("schema_version") != 1 or plan.get("name") != "mcp_queries":
raise ValueError("invalid MCP benchmark plan identity")
if int(plan.get("repeats", 0)) < 3:
raise ValueError("MCP benchmark requires at least three repeats")
cases = plan.get("cases", [])
if not cases:
raise ValueError("MCP benchmark has no cases")
names: set[str] = set()
for case in cases:
name = case.get("name")
if (
not isinstance(name, str)
or not name
or any(not (character.isascii() and (character.isalnum() or character in "-_")) for character in name)
or name in names
):
raise ValueError(f"invalid MCP benchmark case name: {name!r}")
names.add(name)
project_value = case.get("project")
if not isinstance(project_value, str):
raise ValueError(f"invalid MCP benchmark project: {name!r}")
project = (ROOT / project_value).resolve()
if not project.is_relative_to(ROOT) or not project.is_dir():
raise ValueError(f"MCP benchmark project is missing or out of root: {name!r}")
for field in ["expected_impacted_nodes", "expected_cycles", "expected_bridges"]:
if not isinstance(case.get(field), int) or case[field] < 0:
raise ValueError(f"invalid {field}: {case.get('name')!r}")
if case.get("evidence_kind") not in {"node", "edge"}:
raise ValueError(f"invalid evidence_kind: {case.get('name')!r}")
return plan
def run_logged(command: list[str], stdout_path: pathlib.Path, stderr_path: pathlib.Path) -> None:
completed = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False)
stdout_path.write_text(completed.stdout, encoding="utf-8")
stderr_path.write_text(completed.stderr, encoding="utf-8")
if completed.returncode:
raise RuntimeError(completed.stderr or completed.stdout)
def run_measured(
command: list[str],
stdin_path: pathlib.Path,
stdout_path: pathlib.Path,
stderr_path: pathlib.Path,
) -> tuple[int, float, int]:
if not hasattr(os, "wait4"):
raise RuntimeError("RSS measurement requires os.wait4")
started = time.perf_counter()
with (
stdin_path.open("rb") as stdin,
stdout_path.open("wb") as stdout,
stderr_path.open("wb") as stderr,
):
process = subprocess.Popen(command, cwd=ROOT, stdin=stdin, stdout=stdout, stderr=stderr)
_, status, usage = os.wait4(process.pid, 0)
process.returncode = os.waitstatus_to_exitcode(status)
elapsed_ms = round((time.perf_counter() - started) * 1000, 3)
max_rss_kib = int(usage.ru_maxrss)
if sys.platform == "darwin":
max_rss_kib //= 1024
return process.returncode, elapsed_ms, max_rss_kib
def named_node(graph: dict, name: str) -> dict:
matches = [node for node in graph["nodes"] if node.get("name") == name]
if len(matches) != 1:
raise AssertionError(f"expected one node named {name!r}, found {len(matches)}")
return matches[0]
def evidence_entity_id(graph: dict, case: dict) -> str:
if case["evidence_kind"] == "node":
return named_node(graph, case["evidence_name"])["id"]
source = named_node(graph, case["evidence_source"])["id"]
target = named_node(graph, case["evidence_target"])["id"]
matches = [
edge
for edge in graph["edges"]
if edge["source"] == source and edge["target"] == target and edge["kind"] == "calls"
]
if len(matches) != 1:
raise AssertionError(f"expected one evidence edge, found {len(matches)}")
return matches[0]["id"]
def request_messages(start_id: str, entity_id: str) -> list[dict]:
def call(request_id: int, name: str, arguments: dict) -> dict:
return {
"jsonrpc": "2.0",
"id": request_id,
"method": "tools/call",
"params": {"name": name, "arguments": arguments},
}
return [
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": PROTOCOL_VERSION,
"capabilities": {},
"clientInfo": {"name": "brick-benchmark", "version": "1"},
},
},
{"jsonrpc": "2.0", "method": "notifications/initialized"},
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
call(3, "brick_impact", {"node_id": start_id, "direction": "dependents", "max_depth": 2}),
call(4, "brick_cycles", {}),
call(5, "brick_bridges", {}),
call(6, "brick_evidence", {"entity_id": entity_id}),
]
def complete_evidence(edges: list[dict], case_name: str) -> None:
required_edge = {"id", "source", "target", "kind", "evidence"}
required_evidence = {"lane", "backend", "confidence", "provenance"}
for edge in edges:
if not required_edge.issubset(edge):
raise AssertionError(f"{case_name} emitted an incomplete edge")
if not edge["evidence"]:
raise AssertionError(f"{case_name} emitted an edge without evidence")
for evidence in edge["evidence"]:
if set(evidence) != required_evidence:
raise AssertionError(f"{case_name} emitted incomplete evidence: {set(evidence)}")
def validate_responses(stdout_path: pathlib.Path, graph: dict, case: dict) -> dict:
messages = [
json.loads(line)
for line in stdout_path.read_text(encoding="utf-8").splitlines()
if line
]
if len(messages) != 6:
raise AssertionError(f"{case['name']} emitted {len(messages)} responses, expected 6")
if [message.get("id") for message in messages] != [1, 2, 3, 4, 5, 6]:
raise AssertionError(f"{case['name']} response IDs differ")
if messages[0]["result"].get("protocolVersion") != PROTOCOL_VERSION:
raise AssertionError(f"{case['name']} protocol version differs")
tools = messages[1]["result"].get("tools", [])
if [tool.get("name") for tool in tools] != TOOL_NAMES:
raise AssertionError(f"{case['name']} tool inventory differs")
results = [message["result"]["structuredContent"] for message in messages[2:]]
for result, query in zip(
results, ["impact", "cycles", "bridges", "evidence"], strict=True
):
if result.get("query_schema_version") != 1:
raise AssertionError(f"{case['name']} query schema differs")
if result.get("building_schema_version") != 4:
raise AssertionError(f"{case['name']} building schema differs")
if result.get("query") != query:
raise AssertionError(f"{case['name']} query identity differs")
if result.get("scope") != graph["scope"] or result.get("root") != graph["root"]:
raise AssertionError(f"{case['name']} query scope differs")
for message, result in zip(messages[2:], results, strict=True):
if message["result"].get("isError") is not False:
raise AssertionError(f"{case['name']} query unexpectedly failed")
text = json.loads(message["result"]["content"][0]["text"])
if text != result:
raise AssertionError(f"{case['name']} text and structured results differ")
impact, cycles, bridges, evidence = results
observed = {
"impacted_nodes": len(impact["impacted_nodes"]),
"cycles": len(cycles["cycles"]),
"bridges": len(bridges["bridges"]),
}
expected = {
"impacted_nodes": case["expected_impacted_nodes"],
"cycles": case["expected_cycles"],
"bridges": case["expected_bridges"],
}
if observed != expected:
raise AssertionError(f"{case['name']} observations {observed}, expected {expected}")
if bridges.get("graph_semantics") != "weak-undirected-dependency":
raise AssertionError(f"{case['name']} bridge semantics differ")
if evidence.get("entity_kind") != case["evidence_kind"]:
raise AssertionError(f"{case['name']} evidence entity kind differs")
returned_edges = list(impact["edges"])
for cycle in cycles["cycles"]:
returned_edges.extend(cycle["edges"])
returned_edges.extend(entry["edge"] for entry in bridges["bridges"])
if evidence["entity_kind"] == "edge":
returned_edges.append(evidence["edge"])
else:
returned_edges.extend(evidence["incident_edges"])
complete_evidence(returned_edges, case["name"])
return {"results": results, **observed}
def run(out_dir: pathlib.Path | None = None) -> pathlib.Path:
plan = load_plan()
stamp = dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%SZ")
artifact = (out_dir or ROOT / "build" / "benchmarks" / "mcp_queries" / stamp).resolve()
artifact.mkdir(parents=True, exist_ok=False)
binary = ROOT / "target" / "debug" / "brick"
prepare = ["cargo", "build", "--quiet"]
run_logged(prepare, artifact / "prepare.stdout", artifact / "prepare.stderr")
manifest = {
"schema_version": 1,
"started_at": stamp,
"plan": "mcp_queries",
"root": str(ROOT),
"protocol_version": PROTOCOL_VERSION,
"provenance": runner.git_provenance(ROOT),
"host": {"platform": platform.platform(), "python": platform.python_version()},
"prepare_command": prepare,
}
(artifact / "run_manifest.json").write_text(
json.dumps(manifest, indent=2) + "\n", encoding="utf-8"
)
(artifact / "plan.resolved.json").write_text(
json.dumps(plan, indent=2) + "\n", encoding="utf-8"
)
samples: list[dict] = []
signatures: dict[str, str] = {}
repositories = artifact / "repositories"
for case in plan["cases"]:
shutil.copytree(ROOT / case["project"], repositories / case["name"])
for repeat in range(plan["repeats"]):
for case in plan["cases"]:
name = case["name"]
graph_path = artifact / f"{name}-graph-{repeat}.json"
run_logged(
[
str(binary),
"build",
str(repositories / name),
"--json",
str(graph_path),
"--html",
str(artifact / f"{name}-building-{repeat}.html"),
],
artifact / f"{name}-build-{repeat}.stdout",
artifact / f"{name}-build-{repeat}.stderr",
)
graph = json.loads(graph_path.read_text(encoding="utf-8"))
start_id = named_node(graph, case["start_name"])["id"]
entity_id = evidence_entity_id(graph, case)
stdin_path = artifact / f"{name}-requests-{repeat}.jsonl"
stdin_path.write_text(
"".join(json.dumps(message, separators=(",", ":")) + "\n" for message in request_messages(start_id, entity_id)),
encoding="utf-8",
)
stdout_path = artifact / f"{name}-responses-{repeat}.jsonl"
stderr_path = artifact / f"{name}-mcp-{repeat}.stderr"
command = [str(binary), "mcp", str(graph_path)]
returncode, elapsed_ms, max_rss_kib = run_measured(
command, stdin_path, stdout_path, stderr_path
)
if returncode:
raise AssertionError(f"{name} MCP exited {returncode}: {stderr_path.read_text(encoding='utf-8')}")
observations = validate_responses(stdout_path, graph, case)
signature = json.dumps(observations["results"], sort_keys=True, separators=(",", ":"))
if name in signatures and signatures[name] != signature:
raise AssertionError(f"unstable MCP results for {name} repeat {repeat}")
signatures[name] = signature
samples.append(
{
"repeat": repeat,
"case": name,
"command": command,
"returncode": returncode,
"elapsed_ms": elapsed_ms,
"max_rss_kib": max_rss_kib,
"impacted_nodes": observations["impacted_nodes"],
"cycles": observations["cycles"],
"bridges": observations["bridges"],
}
)
cases = {}
for case in plan["cases"]:
case_samples = [sample for sample in samples if sample["case"] == case["name"]]
elapsed = [sample["elapsed_ms"] for sample in case_samples]
rss = [sample["max_rss_kib"] for sample in case_samples]
cases[case["name"]] = {
"identity_stable": True,
"impacted_nodes": case["expected_impacted_nodes"],
"cycles": case["expected_cycles"],
"bridges": case["expected_bridges"],
"elapsed_ms_median": round(statistics.median(elapsed), 3),
"elapsed_ms_min": min(elapsed),
"elapsed_ms_max": max(elapsed),
"max_rss_kib_median": statistics.median(rss),
"max_rss_kib_min": min(rss),
"max_rss_kib_max": max(rss),
}
summary = {
"schema_version": 1,
"plan": "mcp_queries",
"protocol_version": PROTOCOL_VERSION,
"repeats": plan["repeats"],
"cases": cases,
"samples": samples,
}
(artifact / "summary.json").write_text(
json.dumps(summary, indent=2) + "\n", encoding="utf-8"
)
report = ["# Brick benchmark: MCP queries", "", f"- Protocol: {PROTOCOL_VERSION}", ""]
for name, result in cases.items():
report.extend(
[
f"## {name}",
"",
"- Stable results: yes",
f"- Impacted nodes / cycles / bridges: {result['impacted_nodes']} / {result['cycles']} / {result['bridges']}",
f"- Median: {result['elapsed_ms_median']} ms",
f"- Range: {result['elapsed_ms_min']}–{result['elapsed_ms_max']} ms",
f"- Max RSS median: {result['max_rss_kib_median']} KiB",
"",
]
)
(artifact / "REPORT.md").write_text("\n".join(report), encoding="utf-8")
return artifact
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=["run", "self-test"])
parser.add_argument("--out", type=pathlib.Path)
args = parser.parse_args()
plan = load_plan()
if args.action == "self-test":
print(f"MCP benchmark self-test passed ({len(plan['cases'])} cases).")
return 0
artifact = run(args.out)
print(f"ARTIFACT={artifact}")
return 0
if __name__ == "__main__":
raise SystemExit(main())