AtlatestRepositoryfolio
1
#!/usr/bin/env python32
"""Measure the deployed Folio MCP product paths on a disposable estate copy."""4
import argparse5
import hashlib6
import json7
import os8
import resource9
import subprocess10
import time11
from pathlib import Path14
def request(proc, payload):15
started = time.perf_counter_ns()16
proc.stdin.write(json.dumps(payload, separators=(",", ":")) + "\n")17
proc.stdin.flush()18
line = proc.stdout.readline()19
elapsed_ms = (time.perf_counter_ns() - started) / 1_000_00020
if not line:21
raise RuntimeError("Folio closed stdout before replying")22
return elapsed_ms, json.loads(line)25
def summarize_response(response):26
text = ""27
try:28
text = response["result"]["content"][0]["text"]29
except (KeyError, IndexError, TypeError):30
pass31
return {32
"id": response.get("id"),33
"has_result": "result" in response,34
"has_error": "error" in response,35
"content_bytes": len(text.encode("utf-8")),36
"content_sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),37
"markers": {38
"folio_status": "# Folio Status" in text,39
"total_tasks_1474": "Total tasks: 1474" in text,40
"rebuilt_2588": "2588 notes indexed" in text,41
"mutation_added": "Added to inbox:" in text,42
},43
}46
def main() -> int:47
parser = argparse.ArgumentParser()48
parser.add_argument("--binary", required=True)49
parser.add_argument("--root", required=True)50
parser.add_argument("--output", type=Path, required=True)51
args = parser.parse_args()52
env = dict(os.environ, FOLIO_ROOT=args.root)53
before = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss54
wall_start = time.perf_counter_ns()55
proc = subprocess.Popen(56
[args.binary], env=env, text=True, stdin=subprocess.PIPE,57
stdout=subprocess.PIPE, stderr=subprocess.PIPE,58
)59
try:60
startup_ms, initialize = request(proc, {61
"jsonrpc": "2.0", "id": 1, "method": "initialize",62
"params": {"protocolVersion": "2025-11-25", "capabilities": {}},63
})64
proc.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n')65
proc.stdin.flush()66
status_ms, status = request(proc, {67
"jsonrpc": "2.0", "id": 2, "method": "tools/call",68
"params": {"name": "folio/status", "arguments": {}},69
})70
rebuild_ms, rebuild = request(proc, {71
"jsonrpc": "2.0", "id": 3, "method": "tools/call",72
"params": {"name": "folio/rebuild-index", "arguments": {}},73
})74
mutation_ms, mutation = request(proc, {75
"jsonrpc": "2.0", "id": 4, "method": "tools/call",76
"params": {"name": "folio/inbox-add", "arguments": {77
"text": "F0 disposable product-path mutation control", "tags": "f0-positive"78
}},79
})80
second_status_ms, second_status = request(proc, {81
"jsonrpc": "2.0", "id": 5, "method": "tools/call",82
"params": {"name": "folio/status", "arguments": {}},83
})84
finally:85
proc.stdin.close()86
try:87
proc.wait(timeout=5)88
except subprocess.TimeoutExpired:89
proc.terminate()90
proc.wait(timeout=5)91
stderr = proc.stderr.read()92
after = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss93
report = {94
"schema": "folio-f0-product-baseline-v1",95
"binary": str(Path(args.binary).resolve()),96
"root": str(Path(args.root).resolve()),97
"exit_code": proc.returncode,98
"wall_ms": (time.perf_counter_ns() - wall_start) / 1_000_000,99
"max_rss_kib": max(before, after),100
"milliseconds": {101
"cold_start_and_initialize": startup_ms,102
"common_status_query": status_ms,103
"full_index_rebuild": rebuild_ms,104
"controlled_inbox_mutation": mutation_ms,105
"warm_status_query_after_mutation": second_status_ms,106
},107
"controls": {108
"initialize_response": summarize_response(initialize),109
"status_response": summarize_response(status),110
"rebuild_response": summarize_response(rebuild),111
"mutation_response": summarize_response(mutation),112
"post_mutation_status_response": summarize_response(second_status),113
},114
"stderr": stderr,115
}116
args.output.parent.mkdir(parents=True, exist_ok=True)117
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")118
return 0 if proc.returncode == 0 else proc.returncode121
if __name__ == "__main__":122
raise SystemExit(main())