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.
- cpu_perf/__init__.py +15 -0
- cpu_perf/__main__.py +360 -0
- cpu_perf/brain.py +1149 -0
- cpu_perf/context.py +1115 -0
- cpu_perf/corpus/.github/ISSUE_TEMPLATE/add-entry.md +20 -0
- cpu_perf/corpus/.github/ISSUE_TEMPLATE/dead-or-moved-link.md +11 -0
- cpu_perf/corpus/.github/ISSUE_TEMPLATE/evidence-concern.md +11 -0
- cpu_perf/corpus/.github/pull_request_template.md +25 -0
- cpu_perf/corpus/CONTRIBUTING.md +125 -0
- cpu_perf/corpus/LICENSE +21 -0
- cpu_perf/corpus/README.md +743 -0
- cpu_perf/corpus/_build_info.json +259 -0
- cpu_perf/corpus/misc/README.md +19 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/README.md +130 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/bench.c +204 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/results/raw.txt +86 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/results/summary.md +31 -0
- cpu_perf/corpus/misc/benchmarks/02-branch-misprediction/run.sh +101 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/README.md +208 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/bench.c +446 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/build.sh +14 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/results/raw.txt +141 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/results/summary.md +42 -0
- cpu_perf/corpus/misc/benchmarks/03-latency-vs-throughput/run.sh +255 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/README.md +116 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/bench.c +243 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/results/raw.txt +307 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/results/summary.md +52 -0
- cpu_perf/corpus/misc/benchmarks/04-cache-latency/run.sh +145 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/README.md +135 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/bench.c +243 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/build.sh +14 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/results/raw.txt +505 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/results/summary.md +40 -0
- cpu_perf/corpus/misc/benchmarks/05-measurement-pitfalls/run.sh +252 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/README.md +246 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/bench.c +807 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/results/raw.txt +477 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/results/summary.md +69 -0
- cpu_perf/corpus/misc/benchmarks/06-roofline/run.sh +126 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/README.md +286 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/bench.c +498 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/build.sh +42 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/results/raw.txt +162 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/results/summary.md +39 -0
- cpu_perf/corpus/misc/benchmarks/07-aos-vs-soa-simd/run.sh +123 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/README.md +341 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/bench.c +483 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/build.sh +44 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/results/raw.txt +509 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/results/summary.md +64 -0
- cpu_perf/corpus/misc/benchmarks/08-autovectorization-aliasing/run.sh +189 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/README.md +149 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/bench.c +354 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/results/raw.txt +257 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/results/summary.md +50 -0
- cpu_perf/corpus/misc/benchmarks/09-false-sharing/run.sh +144 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/README.md +161 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/bench.c +482 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/results/raw.txt +168 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/results/summary.md +52 -0
- cpu_perf/corpus/misc/benchmarks/10-first-touch/run.sh +146 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/README.md +151 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/bench.c +315 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/results/raw.txt +134 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/results/summary.md +42 -0
- cpu_perf/corpus/misc/benchmarks/11-syscall-cost/run.sh +137 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/README.md +179 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/bench.c +460 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/results/raw.txt +150 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/results/summary.md +34 -0
- cpu_perf/corpus/misc/benchmarks/12-coordinated-omission/run.sh +105 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/README.md +169 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/bench.c +636 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/build.sh +23 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/results/raw.txt +102 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/results/summary.md +51 -0
- cpu_perf/corpus/misc/benchmarks/13-sgemm-naive-vs-blas/run.sh +158 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/README.md +150 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/bench.c +612 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/build.sh +13 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/results/raw.txt +194 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/results/summary.md +35 -0
- cpu_perf/corpus/misc/benchmarks/14-pcore-vs-ecore/run.sh +187 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/README.md +137 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/bench.c +426 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/build.sh +12 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/results/raw.txt +189 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/results/summary.md +29 -0
- cpu_perf/corpus/misc/benchmarks/15-stream-bandwidth/run.sh +140 -0
- cpu_perf/corpus/misc/benchmarks/README.md +72 -0
- cpu_perf/corpus/misc/benchmarks/common/clock_estimate.c +46 -0
- cpu_perf/corpus/misc/benchmarks/common/machine.sh +30 -0
- cpu_perf/corpus/misc/benchmarks/common/refresh_readme.py +28 -0
- cpu_perf/corpus/misc/benchmarks/common/timing.h +75 -0
- cpu_perf/corpus/misc/benchmarks/compile_all.sh +18 -0
- cpu_perf/corpus/misc/benchmarks/run_all.sh +19 -0
- cpu_perf/corpus/misc/notes/banner/README.md +63 -0
- cpu_perf/corpus/misc/notes/benchmark-brief.md +97 -0
- cpu_perf/corpus/misc/notes/changelog.md +881 -0
- cpu_perf/corpus/misc/notes/conventions.md +126 -0
- cpu_perf/corpus/misc/notes/owner-brief.md +102 -0
- cpu_perf/corpus/misc/notes/sections/01-start-here.md +78 -0
- cpu_perf/corpus/misc/notes/sections/02-one-instruction.md +99 -0
- cpu_perf/corpus/misc/notes/sections/03-microarchitecture.md +114 -0
- cpu_perf/corpus/misc/notes/sections/04-memory-hierarchy.md +101 -0
- cpu_perf/corpus/misc/notes/sections/05-measurement.md +95 -0
- cpu_perf/corpus/misc/notes/sections/06-models.md +119 -0
- cpu_perf/corpus/misc/notes/sections/07-single-thread.md +118 -0
- cpu_perf/corpus/misc/notes/sections/08-compilers.md +113 -0
- cpu_perf/corpus/misc/notes/sections/09-concurrency.md +140 -0
- cpu_perf/corpus/misc/notes/sections/10-numa.md +104 -0
- cpu_perf/corpus/misc/notes/sections/11-os-and-io.md +127 -0
- cpu_perf/corpus/misc/notes/sections/12-tail-latency.md +129 -0
- cpu_perf/corpus/misc/notes/sections/13-inference-on-cpu.md +145 -0
- cpu_perf/corpus/misc/notes/sections/14-hardware-generations.md +140 -0
- cpu_perf/corpus/misc/notes/sections/15-benchmarks.md +109 -0
- cpu_perf/corpus/misc/notes/sections/16-watchlist.md +132 -0
- cpu_perf/corpus/misc/notes/status.md +183 -0
- cpu_perf/corpus/misc/notes/voice.md +52 -0
- cpu_perf/corpus/misc/scripts/build_changelog.py +86 -0
- cpu_perf/corpus/misc/scripts/check_format.py +256 -0
- cpu_perf/corpus/misc/scripts/check_links.py +287 -0
- cpu_perf/corpus.py +344 -0
- cpu_perf/corpus_update.py +286 -0
- cpu_perf/evidence.py +163 -0
- cpu_perf/grammar.py +70 -0
- cpu_perf/library/__init__.py +2 -0
- cpu_perf/library/chunk.py +85 -0
- cpu_perf/library/crawler.py +475 -0
- cpu_perf/library/embed.py +104 -0
- cpu_perf/library/extract.py +516 -0
- cpu_perf/library/resolve.py +172 -0
- cpu_perf/library/retrieve.py +409 -0
- cpu_perf/library/service.py +445 -0
- cpu_perf/library/store.py +601 -0
- cpu_perf/locate.py +180 -0
- cpu_perf/manifest.py +180 -0
- cpu_perf/models.py +316 -0
- cpu_perf/net.py +405 -0
- cpu_perf/parse_benchmarks.py +165 -0
- cpu_perf/parse_readme.py +237 -0
- cpu_perf/parse_record.py +257 -0
- cpu_perf/prompts.py +124 -0
- cpu_perf/render.py +339 -0
- cpu_perf/resolve.py +166 -0
- cpu_perf/schema.py +162 -0
- cpu_perf/search.py +367 -0
- cpu_perf/server.py +471 -0
- cpu_perf/snippets.py +76 -0
- cpu_perf/text.py +260 -0
- cpu_perf-0.1.0.dist-info/METADATA +420 -0
- cpu_perf-0.1.0.dist-info/RECORD +162 -0
- cpu_perf-0.1.0.dist-info/WHEEL +4 -0
- 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())
|