(name: str, root: Path, sample_symbols: list[str])
| 273 | |
| 274 | |
| 275 | def benchmark_tracedecay(name: str, root: Path, sample_symbols: list[str]) -> dict: |
| 276 | out: dict = {} |
| 277 | ts_dir = root / ".tracedecay" |
| 278 | if ts_dir.exists(): |
| 279 | shutil.rmtree(ts_dir) |
| 280 | |
| 281 | with tempfile.TemporaryDirectory(prefix=f"tracedecay-bench-{name}-") as tmp: |
| 282 | data_dir = Path(tmp) / ".tracedecay" |
| 283 | data_dir.mkdir(parents=True, exist_ok=True) |
| 284 | env = dict(os.environ) |
| 285 | env["TRACEDECAY_DATA_DIR"] = str(data_dir) |
| 286 | env["TRACEDECAY_GLOBAL_DB"] = str(data_dir / "global.db") |
| 287 | |
| 288 | log(f"[tk] {name}: init (cold) ...") |
| 289 | dt, mem, cp = run_timed([TRACEDECAY_BIN, "init"], cwd=str(root), env=env) |
| 290 | if cp.returncode != 0: |
| 291 | log(f"[tk] init failed: {cp.stderr[-400:]}") |
| 292 | out["error"] = cp.stderr[-2000:] |
| 293 | return out |
| 294 | out["cold_index_seconds"] = round(dt, 3) |
| 295 | out["cold_index_peak_memory_bytes"] = mem |
| 296 | |
| 297 | log(f"[tk] {name}: sync (warm) ...") |
| 298 | dt, _, cp = run_timed([TRACEDECAY_BIN, "sync"], cwd=str(root), env=env) |
| 299 | out["warm_index_seconds"] = round(dt, 3) if cp.returncode == 0 else None |
| 300 | |
| 301 | status: dict = {} |
| 302 | _, _, cp = run_timed([TRACEDECAY_BIN, "status", "--json"], cwd=str(root), env=env) |
| 303 | if cp.returncode == 0: |
| 304 | try: |
| 305 | status = json.loads(cp.stdout) |
| 306 | out["node_count"] = status.get("node_count") |
| 307 | out["edge_count"] = status.get("edge_count") |
| 308 | out["file_count"] = status.get("file_count") |
| 309 | out["files_by_language"] = status.get("files_by_language") |
| 310 | except json.JSONDecodeError: |
| 311 | pass |
| 312 | |
| 313 | log(f"[tk] {name}: query benchmarks via MCP ({len(sample_symbols)} symbols) ...") |
| 314 | queries = [sym.split(".")[-1] for sym in sample_symbols] |
| 315 | |
| 316 | def handler_us(resp: dict) -> int | None: |
| 317 | meta = resp.get("result", {}).get("_meta", {}) |
| 318 | v = meta.get("duration_us") |
| 319 | return int(v) if isinstance(v, (int, float)) else None |
| 320 | |
| 321 | def avg_ms(samples: list[int]) -> float | None: |
| 322 | return round(sum(samples) / len(samples) / 1000.0, 3) if samples else None |
| 323 | |
| 324 | with TraceDecayMcp(root, env=env) as mcp: |
| 325 | # Warm-up so first-query DB/cache fill doesn't skew the averages. |
| 326 | # `_meta.duration_us` already excludes transport overhead, but the |
| 327 | # first call also pays one-time index warmup we want to drop. |
| 328 | if queries: |
| 329 | mcp.call_tool("tracedecay_search", {"query": queries[0], "limit": 5}) |
| 330 | |
| 331 | # Apples-to-apples vs token-savior's find_symbol: bare-name index |
| 332 | # probe via `tracedecay_find_exact_symbol`, not BM25-ranked `search`. |
no test coverage detected