cpu-perf 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. cpu_perf/__init__.py +15 -0
  2. cpu_perf/__main__.py +360 -0
  3. cpu_perf/brain.py +1149 -0
  4. cpu_perf/context.py +1115 -0
  5. cpu_perf/corpus/.github/ISSUE_TEMPLATE/add-entry.md +20 -0
  6. cpu_perf/corpus/.github/ISSUE_TEMPLATE/dead-or-moved-link.md +11 -0
  7. cpu_perf/corpus/.github/ISSUE_TEMPLATE/evidence-concern.md +11 -0
  8. cpu_perf/corpus/.github/pull_request_template.md +25 -0
  9. cpu_perf/corpus/CONTRIBUTING.md +125 -0
  10. cpu_perf/corpus/LICENSE +21 -0
  11. cpu_perf/corpus/README.md +743 -0
  12. cpu_perf/corpus/_build_info.json +259 -0
  13. cpu_perf/corpus/misc/README.md +19 -0
  14. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/README.md +130 -0
  15. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/bench.c +204 -0
  16. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/build.sh +12 -0
  17. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/results/raw.txt +86 -0
  18. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/results/summary.md +31 -0
  19. cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/run.sh +101 -0
  20. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/README.md +208 -0
  21. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/bench.c +446 -0
  22. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/build.sh +14 -0
  23. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/results/raw.txt +141 -0
  24. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/results/summary.md +42 -0
  25. cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/run.sh +255 -0
  26. cpu_perf/corpus/misc/benchmarks/04-cache-latency/README.md +116 -0
  27. cpu_perf/corpus/misc/benchmarks/04-cache-latency/bench.c +243 -0
  28. cpu_perf/corpus/misc/benchmarks/04-cache-latency/build.sh +12 -0
  29. cpu_perf/corpus/misc/benchmarks/04-cache-latency/results/raw.txt +307 -0
  30. cpu_perf/corpus/misc/benchmarks/04-cache-latency/results/summary.md +52 -0
  31. cpu_perf/corpus/misc/benchmarks/04-cache-latency/run.sh +145 -0
  32. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/README.md +135 -0
  33. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/bench.c +243 -0
  34. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/build.sh +14 -0
  35. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/results/raw.txt +505 -0
  36. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/results/summary.md +40 -0
  37. cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/run.sh +252 -0
  38. cpu_perf/corpus/misc/benchmarks/06-roofline/README.md +246 -0
  39. cpu_perf/corpus/misc/benchmarks/06-roofline/bench.c +807 -0
  40. cpu_perf/corpus/misc/benchmarks/06-roofline/build.sh +12 -0
  41. cpu_perf/corpus/misc/benchmarks/06-roofline/results/raw.txt +477 -0
  42. cpu_perf/corpus/misc/benchmarks/06-roofline/results/summary.md +69 -0
  43. cpu_perf/corpus/misc/benchmarks/06-roofline/run.sh +126 -0
  44. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/README.md +286 -0
  45. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/bench.c +498 -0
  46. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/build.sh +42 -0
  47. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/results/raw.txt +162 -0
  48. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/results/summary.md +39 -0
  49. cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/run.sh +123 -0
  50. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/README.md +341 -0
  51. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/bench.c +483 -0
  52. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/build.sh +44 -0
  53. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/results/raw.txt +509 -0
  54. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/results/summary.md +64 -0
  55. cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/run.sh +189 -0
  56. cpu_perf/corpus/misc/benchmarks/09-false-sharing/README.md +149 -0
  57. cpu_perf/corpus/misc/benchmarks/09-false-sharing/bench.c +354 -0
  58. cpu_perf/corpus/misc/benchmarks/09-false-sharing/build.sh +12 -0
  59. cpu_perf/corpus/misc/benchmarks/09-false-sharing/results/raw.txt +257 -0
  60. cpu_perf/corpus/misc/benchmarks/09-false-sharing/results/summary.md +50 -0
  61. cpu_perf/corpus/misc/benchmarks/09-false-sharing/run.sh +144 -0
  62. cpu_perf/corpus/misc/benchmarks/10-first-touch/README.md +161 -0
  63. cpu_perf/corpus/misc/benchmarks/10-first-touch/bench.c +482 -0
  64. cpu_perf/corpus/misc/benchmarks/10-first-touch/build.sh +12 -0
  65. cpu_perf/corpus/misc/benchmarks/10-first-touch/results/raw.txt +168 -0
  66. cpu_perf/corpus/misc/benchmarks/10-first-touch/results/summary.md +52 -0
  67. cpu_perf/corpus/misc/benchmarks/10-first-touch/run.sh +146 -0
  68. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/README.md +151 -0
  69. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/bench.c +315 -0
  70. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/build.sh +12 -0
  71. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/results/raw.txt +134 -0
  72. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/results/summary.md +42 -0
  73. cpu_perf/corpus/misc/benchmarks/11-syscall-cost/run.sh +137 -0
  74. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/README.md +179 -0
  75. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/bench.c +460 -0
  76. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/build.sh +12 -0
  77. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/results/raw.txt +150 -0
  78. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/results/summary.md +34 -0
  79. cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/run.sh +105 -0
  80. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/README.md +169 -0
  81. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/bench.c +636 -0
  82. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/build.sh +23 -0
  83. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/results/raw.txt +102 -0
  84. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/results/summary.md +51 -0
  85. cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/run.sh +158 -0
  86. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/README.md +150 -0
  87. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/bench.c +612 -0
  88. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/build.sh +13 -0
  89. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/results/raw.txt +194 -0
  90. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/results/summary.md +35 -0
  91. cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/run.sh +187 -0
  92. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/README.md +137 -0
  93. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/bench.c +426 -0
  94. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/build.sh +12 -0
  95. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/results/raw.txt +189 -0
  96. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/results/summary.md +29 -0
  97. cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/run.sh +140 -0
  98. cpu_perf/corpus/misc/benchmarks/README.md +72 -0
  99. cpu_perf/corpus/misc/benchmarks/common/clock_estimate.c +46 -0
  100. cpu_perf/corpus/misc/benchmarks/common/machine.sh +30 -0
  101. cpu_perf/corpus/misc/benchmarks/common/refresh_readme.py +28 -0
  102. cpu_perf/corpus/misc/benchmarks/common/timing.h +75 -0
  103. cpu_perf/corpus/misc/benchmarks/compile_all.sh +18 -0
  104. cpu_perf/corpus/misc/benchmarks/run_all.sh +19 -0
  105. cpu_perf/corpus/misc/notes/banner/README.md +63 -0
  106. cpu_perf/corpus/misc/notes/benchmark-brief.md +97 -0
  107. cpu_perf/corpus/misc/notes/changelog.md +881 -0
  108. cpu_perf/corpus/misc/notes/conventions.md +126 -0
  109. cpu_perf/corpus/misc/notes/owner-brief.md +102 -0
  110. cpu_perf/corpus/misc/notes/sections/01-start-here.md +78 -0
  111. cpu_perf/corpus/misc/notes/sections/02-one-instruction.md +99 -0
  112. cpu_perf/corpus/misc/notes/sections/03-microarchitecture.md +114 -0
  113. cpu_perf/corpus/misc/notes/sections/04-memory-hierarchy.md +101 -0
  114. cpu_perf/corpus/misc/notes/sections/05-measurement.md +95 -0
  115. cpu_perf/corpus/misc/notes/sections/06-models.md +119 -0
  116. cpu_perf/corpus/misc/notes/sections/07-single-thread.md +118 -0
  117. cpu_perf/corpus/misc/notes/sections/08-compilers.md +113 -0
  118. cpu_perf/corpus/misc/notes/sections/09-concurrency.md +140 -0
  119. cpu_perf/corpus/misc/notes/sections/10-numa.md +104 -0
  120. cpu_perf/corpus/misc/notes/sections/11-os-and-io.md +127 -0
  121. cpu_perf/corpus/misc/notes/sections/12-tail-latency.md +129 -0
  122. cpu_perf/corpus/misc/notes/sections/13-inference-on-cpu.md +145 -0
  123. cpu_perf/corpus/misc/notes/sections/14-hardware-generations.md +140 -0
  124. cpu_perf/corpus/misc/notes/sections/15-benchmarks.md +109 -0
  125. cpu_perf/corpus/misc/notes/sections/16-watchlist.md +132 -0
  126. cpu_perf/corpus/misc/notes/status.md +183 -0
  127. cpu_perf/corpus/misc/notes/voice.md +52 -0
  128. cpu_perf/corpus/misc/scripts/build_changelog.py +86 -0
  129. cpu_perf/corpus/misc/scripts/check_format.py +256 -0
  130. cpu_perf/corpus/misc/scripts/check_links.py +287 -0
  131. cpu_perf/corpus.py +344 -0
  132. cpu_perf/corpus_update.py +286 -0
  133. cpu_perf/evidence.py +163 -0
  134. cpu_perf/grammar.py +70 -0
  135. cpu_perf/library/__init__.py +2 -0
  136. cpu_perf/library/chunk.py +85 -0
  137. cpu_perf/library/crawler.py +475 -0
  138. cpu_perf/library/embed.py +104 -0
  139. cpu_perf/library/extract.py +516 -0
  140. cpu_perf/library/resolve.py +172 -0
  141. cpu_perf/library/retrieve.py +409 -0
  142. cpu_perf/library/service.py +445 -0
  143. cpu_perf/library/store.py +601 -0
  144. cpu_perf/locate.py +180 -0
  145. cpu_perf/manifest.py +180 -0
  146. cpu_perf/models.py +316 -0
  147. cpu_perf/net.py +405 -0
  148. cpu_perf/parse_benchmarks.py +165 -0
  149. cpu_perf/parse_readme.py +237 -0
  150. cpu_perf/parse_record.py +257 -0
  151. cpu_perf/prompts.py +124 -0
  152. cpu_perf/render.py +339 -0
  153. cpu_perf/resolve.py +166 -0
  154. cpu_perf/schema.py +162 -0
  155. cpu_perf/search.py +367 -0
  156. cpu_perf/server.py +471 -0
  157. cpu_perf/snippets.py +76 -0
  158. cpu_perf/text.py +260 -0
  159. cpu_perf-0.1.0.dist-info/METADATA +420 -0
  160. cpu_perf-0.1.0.dist-info/RECORD +162 -0
  161. cpu_perf-0.1.0.dist-info/WHEEL +4 -0
  162. cpu_perf-0.1.0.dist-info/entry_points.txt +2 -0
cpu_perf/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """cpu-perf: the CPU Performance Engineering list as an MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from importlib.metadata import PackageNotFoundError, version
7
+
8
+ try:
9
+ __version__ = version("cpu-perf")
10
+ except PackageNotFoundError: # running from a checkout without an install
11
+ __version__ = "0+checkout"
12
+ except ImportError: # pragma: no cover
13
+ __version__ = "0+checkout"
14
+
15
+ REPO_URL = "https://github.com/usamahz/cpu-performance-engineering"
cpu_perf/__main__.py ADDED
@@ -0,0 +1,360 @@
1
+ """Command line: serve (stdio or HTTP), build the library, show status, self-test."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import logging
8
+ import os
9
+ import signal
10
+ import statistics
11
+ import sys
12
+ import threading
13
+ import time
14
+
15
+ from . import __version__
16
+
17
+
18
+ def _env_bool(name: str, default: bool) -> bool:
19
+ val = os.environ.get(name)
20
+ if val is None:
21
+ return default
22
+ return val.strip().lower() not in ("0", "false", "no", "off", "")
23
+
24
+
25
+ def _common(p: argparse.ArgumentParser) -> None:
26
+ p.add_argument("--repo", default=os.environ.get("CPU_PERF_REPO"), help="serve a checkout of the repository instead of the bundled copy")
27
+ p.add_argument("--data-dir", default=os.environ.get("CPU_PERF_DATA_DIR"), help="where the source library lives")
28
+ p.add_argument("--embed-model", default=os.environ.get("CPU_PERF_EMBED_MODEL"), help="model2vec model id, 'hashing', or 'none' for keyword search only")
29
+ p.add_argument("--no-library", action="store_true", default=not _env_bool("CPU_PERF_LIBRARY", True), help="repository knowledge only: no source library")
30
+ p.add_argument(
31
+ "--respect-robots", action="store_true", default=_env_bool("CPU_PERF_RESPECT_ROBOTS", False),
32
+ help="skip what robots.txt disallows (by default the listed links are read, as a reader opening them would)",
33
+ )
34
+ p.add_argument("--ignore-robots", action="store_true", help=argparse.SUPPRESS) # the default now; still accepted
35
+ p.add_argument("--no-auto-update", action="store_true", default=not _env_bool("CPU_PERF_AUTO_UPDATE", True), help="serve the installed copy of the list; no daily check for a newer one")
36
+ p.add_argument("--log-level", default=os.environ.get("CPU_PERF_LOG_LEVEL", "WARNING"))
37
+
38
+
39
+ def build_parser() -> argparse.ArgumentParser:
40
+ parser = argparse.ArgumentParser(prog="cpu-perf", description="MCP server for the CPU Performance Engineering list.")
41
+ parser.add_argument("--version", action="version", version=f"cpu-perf {__version__}")
42
+ parser.add_argument("--selftest", action="store_true", help="load the corpus, check it, time it, and exit")
43
+ sub = parser.add_subparsers(dest="command")
44
+
45
+ serve = sub.add_parser("serve", help="run the MCP server (default)")
46
+ _common(serve)
47
+ serve.add_argument("--transport", choices=("stdio", "http"), default=os.environ.get("CPU_PERF_TRANSPORT", "stdio"))
48
+ serve.add_argument("--host", default=os.environ.get("CPU_PERF_HOST", "127.0.0.1"))
49
+ serve.add_argument("--port", type=int, default=int(os.environ.get("CPU_PERF_PORT") or os.environ.get("PORT") or 8000))
50
+ serve.add_argument("--path", default=os.environ.get("CPU_PERF_PATH", "/mcp"))
51
+ serve.add_argument("--no-auto-index", action="store_true", default=not _env_bool("CPU_PERF_AUTO_INDEX", True), help="do not build the library in the background")
52
+ serve.add_argument("--no-live-fetch", action="store_true", default=not _env_bool("CPU_PERF_LIVE_FETCH", True), help="read_source serves only what is already indexed")
53
+ serve.add_argument("--structured-output", action="store_true", default=_env_bool("CPU_PERF_STRUCTURED_OUTPUT", False), help="also return structured content and output schemas (for programmatic clients; chat clients read the markdown)")
54
+ serve.add_argument("--allowed-host", action="append", default=[h for h in os.environ.get("CPU_PERF_ALLOWED_HOSTS", "").split(",") if h], help="Host header values to accept over HTTP (DNS-rebinding protection)")
55
+
56
+ index = sub.add_parser("index", help="build or refresh the source library now")
57
+ _common(index)
58
+ index.add_argument("--refresh", action="store_true", help="re-check every source, not only missing or stale ones")
59
+ index.add_argument("--only", action="append", default=[], help="a URL or entry id to (re)fetch; repeatable")
60
+ index.add_argument("--max-pages", type=int, default=2500, help="pages indexed per PDF")
61
+ index.add_argument("--workers", type=int, default=6)
62
+ index.add_argument("--no-embeddings", action="store_true", help="keyword index only")
63
+
64
+ status = sub.add_parser("status", help="show the library's coverage")
65
+ _common(status)
66
+ status.add_argument("--detail", action="store_true")
67
+ status.add_argument("--json", action="store_true")
68
+ return parser
69
+
70
+
71
+ def _setup_logging(level: str) -> None:
72
+ logging.basicConfig(stream=sys.stderr, level=getattr(logging, str(level).upper(), logging.WARNING), format="cpu-perf %(levelname)s %(message)s")
73
+
74
+
75
+ def _data_dir(args):
76
+ from pathlib import Path
77
+
78
+ from .library.store import default_data_dir
79
+
80
+ return Path(args.data_dir).expanduser() if args.data_dir else default_data_dir()
81
+
82
+
83
+ def _brain(args, *, auto_index: bool, live_fetch: bool, library: bool = True, workers: int = 6, max_pages: int = 2500):
84
+ from .brain import Brain
85
+ from .corpus import load
86
+ from .library.service import LibraryService
87
+ from .locate import locate
88
+ from .search import SearchIndex
89
+
90
+ # with the daily update on, a newer list it downloaded wins over the installed copy
91
+ reader = locate(args.repo, data_dir=None if args.no_auto_update else _data_dir(args))
92
+ reader.preload()
93
+ corpus = load(reader)
94
+ index = SearchIndex(corpus)
95
+ lib = None
96
+ if library and not args.no_library:
97
+ lib = LibraryService(
98
+ corpus,
99
+ index.expand,
100
+ data_dir=args.data_dir,
101
+ embed_model=args.embed_model,
102
+ auto_index=auto_index,
103
+ live_fetch=live_fetch,
104
+ respect_robots=args.respect_robots and not args.ignore_robots,
105
+ workers=workers,
106
+ max_pages=max_pages,
107
+ )
108
+ return Brain(corpus, index, lib)
109
+
110
+
111
+ def validate_corpus(reader, current):
112
+ """A downloaded list must parse and must not have collapsed against the one served."""
113
+ from .corpus import load
114
+
115
+ reader.preload()
116
+ corpus = load(reader)
117
+ new, old = corpus.stats(), current.stats()
118
+ for key in ("sections", "linked_entries", "benchmarks"):
119
+ if new[key] < 0.8 * old[key]:
120
+ raise ValueError(f"{key} fell from {old[key]} to {new[key]}")
121
+ return corpus
122
+
123
+
124
+ def start_daily_update(holder, args, updater=None):
125
+ """Check once a day for a newer list and swap it in; returns the updater or None."""
126
+ from .brain import Brain
127
+ from .corpus_update import CorpusUpdater
128
+ from .search import SearchIndex
129
+
130
+ brain = holder.current
131
+ if args.no_auto_update or brain.c.reader.source not in ("bundled", "downloaded"):
132
+ return None
133
+ updater = updater or CorpusUpdater(_data_dir(args))
134
+ brain.updater = updater
135
+ log = logging.getLogger("cpu_perf.update")
136
+
137
+ def tick():
138
+ cur = holder.current
139
+ built = {}
140
+
141
+ def check(reader):
142
+ built["corpus"] = validate_corpus(reader, cur.c)
143
+
144
+ newer = updater.run(cur.c.reader, validate=check)
145
+ if newer is None:
146
+ return
147
+ corpus = built.get("corpus") or validate_corpus(newer, cur.c)
148
+ holder.swap(Brain(corpus, SearchIndex(corpus), cur.lib))
149
+ log.warning("now serving the list at commit %s", (newer.commit or "")[:12])
150
+
151
+ if brain.lib is not None:
152
+ brain.lib.on_tick = tick
153
+ else:
154
+ def loop():
155
+ while True:
156
+ try:
157
+ tick()
158
+ except Exception:
159
+ log.exception("list update failed")
160
+ time.sleep(float(os.environ.get("CPU_PERF_MAINTENANCE_SECONDS", "300")))
161
+
162
+ threading.Thread(target=loop, name="list-update", daemon=True).start()
163
+ return updater
164
+
165
+
166
+ def command() -> str:
167
+ """How the person started this program, for the commands we suggest: uvx
168
+ runs it from uv's cache, where there is no `cpu-perf` on the PATH."""
169
+ path = os.path.abspath(sys.argv[0] or "").replace("\\", "/")
170
+ return "uvx cpu-perf" if "/uv/" in path else "cpu-perf"
171
+
172
+
173
+ def terminal_hint() -> str:
174
+ c = command()
175
+ return f"""cpu-perf is an MCP server: your AI client starts it and talks to it over stdin.
176
+ Add it to a client instead of running it here:
177
+
178
+ claude mcp add --scope user cpu-perf -- uvx cpu-perf
179
+ codex mcp add cpu-perf -- uvx cpu-perf
180
+
181
+ From a terminal: `{c} status` shows the source library, `{c} index`
182
+ builds it now, `{c} --help` lists the rest. Waiting for a client on
183
+ stdin; Ctrl-C quits."""
184
+
185
+
186
+ def _leave(holder, code: int) -> None:
187
+ """Exit now. Downloads still in flight would otherwise hold the process
188
+ open for minutes after its client has gone; every library write is
189
+ already committed, the operating system releases the crawl lock, and the
190
+ crawl resumes where it stopped on the next start."""
191
+ lib = holder.current.lib
192
+ if lib is not None:
193
+ lib.stop()
194
+ logging.shutdown()
195
+ for stream in (sys.stdout, sys.stderr):
196
+ try:
197
+ stream.flush()
198
+ except (OSError, ValueError):
199
+ pass
200
+ os._exit(code)
201
+
202
+
203
+ def cmd_serve(args) -> int:
204
+ from .brain import BrainHolder
205
+ from .server import create_server
206
+
207
+ brain = _brain(args, auto_index=not args.no_auto_index, live_fetch=not args.no_live_fetch)
208
+ holder = BrainHolder(brain)
209
+ srv = create_server(holder, live_fetch=not args.no_live_fetch, structured=args.structured_output)
210
+ start_daily_update(holder, args)
211
+ if brain.lib is not None:
212
+ brain.lib.start()
213
+ if args.transport == "stdio":
214
+ if sys.stdin.isatty():
215
+ print(terminal_hint(), file=sys.stderr)
216
+ # Ctrl-C: the stdio transport waits on a stdin read that a terminal never ends
217
+ signal.signal(signal.SIGINT, lambda *_: _leave(holder, 130))
218
+ srv.run("stdio")
219
+ _leave(holder, 0) # the client closed stdin
220
+ from mcp.server.transport_security import TransportSecuritySettings
221
+
222
+ security = None
223
+ if args.allowed_host:
224
+ hosts = list(args.allowed_host)
225
+ security = TransportSecuritySettings(
226
+ enable_dns_rebinding_protection=True,
227
+ allowed_hosts=hosts + [f"{h}:*" for h in hosts if ":" not in h],
228
+ allowed_origins=[f"https://{h}" for h in hosts] + [f"http://{h}" for h in hosts],
229
+ )
230
+ elif args.host not in ("127.0.0.1", "localhost", "::1"):
231
+ logging.getLogger("cpu_perf").warning("serving on %s without --allowed-host: DNS-rebinding protection is off", args.host)
232
+ security = TransportSecuritySettings(enable_dns_rebinding_protection=False)
233
+ signal.signal(signal.SIGINT, lambda *_: _leave(holder, 130)) # the web server would re-raise it, then wait on downloads
234
+ srv.run(
235
+ "streamable-http",
236
+ host=args.host,
237
+ port=args.port,
238
+ streamable_http_path=args.path,
239
+ json_response=True,
240
+ stateless_http=True,
241
+ transport_security=security,
242
+ )
243
+ _leave(holder, 0)
244
+
245
+
246
+ def cmd_index(args) -> int:
247
+ brain = _brain(args, auto_index=False, live_fetch=False, workers=args.workers, max_pages=args.max_pages)
248
+ lib = brain.lib
249
+ if lib is None:
250
+ print("the library is disabled (--no-library)", file=sys.stderr)
251
+ return 2
252
+ if args.no_embeddings:
253
+ lib.embed_model = "none"
254
+ print(f"library: {lib.data_dir}", file=sys.stderr)
255
+ print("loading the embedding model...", file=sys.stderr)
256
+ lib.load_embedder()
257
+ print(f"embedder: {lib.embedder_state}", file=sys.stderr)
258
+ if not lib.lock.acquire():
259
+ print("another process is building the library; try again later", file=sys.stderr)
260
+ return 1
261
+ stop = threading.Event()
262
+
263
+ def report():
264
+ while not stop.wait(3):
265
+ p = lib.crawler.progress
266
+ cur = ", ".join(u.split("/")[2] for u in p.current[:3])
267
+ print(f" {p.done}/{p.total} sources {dict(p.counts)} now: {cur}", file=sys.stderr)
268
+
269
+ t = threading.Thread(target=report, daemon=True)
270
+ t.start()
271
+ try:
272
+ if lib.store.stale_normalisation():
273
+ print(f"re-normalised {lib.renormalise()} passages for the current tokenizer", file=sys.stderr)
274
+ summary = lib.crawler.run(only=args.only or None, refresh=args.refresh)
275
+ except KeyboardInterrupt:
276
+ stop.set()
277
+ p = lib.crawler.progress
278
+ print(f"\nstopped at {p.done}/{p.total} sources; run `{command()} index` again to continue", file=sys.stderr)
279
+ os._exit(130) # downloads in flight would hold the process open; what is done is committed
280
+ finally:
281
+ stop.set()
282
+ lib.lock.release()
283
+ print(f"done: {summary['done']}/{summary['total']} {summary['counts']}", file=sys.stderr)
284
+ print(lib.coverage_line())
285
+ return 0
286
+
287
+
288
+ def cmd_status(args) -> int:
289
+ from . import render
290
+
291
+ brain = _brain(args, auto_index=False, live_fetch=False)
292
+ out = brain.library_status(args.detail)
293
+ if args.json:
294
+ print(json.dumps(out.model_dump(mode="json"), indent=1))
295
+ else:
296
+ print(render.status(out, cli=command()))
297
+ return 0
298
+
299
+
300
+ def cmd_selftest(args) -> int:
301
+ from .corpus import load
302
+ from .locate import locate
303
+ from .search import SearchIndex
304
+
305
+ t0 = time.perf_counter()
306
+ corpus = load(locate(getattr(args, "repo", None) or os.environ.get("CPU_PERF_REPO")))
307
+ t1 = time.perf_counter()
308
+ index = SearchIndex(corpus)
309
+ t2 = time.perf_counter()
310
+ stats = corpus.stats()
311
+ problems = list(corpus.warnings)
312
+ if corpus.badge_entries is not None and corpus.badge_entries != stats["linked_entries"]:
313
+ problems.append("entry count differs from the README badge")
314
+ if corpus.badge_benchmarks is not None and corpus.badge_benchmarks != stats["benchmarks"]:
315
+ problems.append("benchmark count differs from the README badge")
316
+ queries = ["false sharing", "branch misprediction penalty", "numa first touch", "roofline", "tail latency p99",
317
+ "auto-vectorisation aliasing", "int8 quantization", "store forwarding", "huge pages tlb", "top-down"]
318
+ times = []
319
+ for _ in range(20):
320
+ for q in queries:
321
+ s = time.perf_counter()
322
+ index.search(q, limit=10)
323
+ times.append((time.perf_counter() - s) * 1000)
324
+ times.sort()
325
+ print(f"cpu-perf {__version__}")
326
+ print(f"source: {corpus.reader.source} ({corpus.reader.describe()})")
327
+ for k, v in stats.items():
328
+ print(f"{k}: {v}")
329
+ print(f"load_ms: {(t1 - t0) * 1000:.1f}")
330
+ print(f"index_ms: {(t2 - t1) * 1000:.1f}")
331
+ print(f"query_ms p50: {statistics.median(times):.3f} p99: {times[int(len(times) * 0.99) - 1]:.3f}")
332
+ for p in problems:
333
+ print(f"PROBLEM: {p}")
334
+ return 1 if problems else 0
335
+
336
+
337
+ def main(argv: list[str] | None = None) -> int:
338
+ argv = list(sys.argv[1:] if argv is None else argv)
339
+ parser = build_parser()
340
+ commands = {"serve", "index", "status"}
341
+ if "--selftest" in argv:
342
+ args = parser.parse_args(["--selftest"])
343
+ rest = [a for a in argv if a != "--selftest"]
344
+ args.repo = None
345
+ if "--repo" in rest:
346
+ args.repo = rest[rest.index("--repo") + 1]
347
+ return cmd_selftest(args)
348
+ if not argv or argv[0] not in commands and not argv[0] in ("-h", "--help", "--version"):
349
+ argv = ["serve", *argv]
350
+ args = parser.parse_args(argv)
351
+ _setup_logging(getattr(args, "log_level", "WARNING"))
352
+ if args.command == "index":
353
+ return cmd_index(args)
354
+ if args.command == "status":
355
+ return cmd_status(args)
356
+ return cmd_serve(args)
357
+
358
+
359
+ if __name__ == "__main__":
360
+ sys.exit(main())