mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
vscode_optimizer: split _optimize.py 498 -> 100 + 5 modules
The last python file over the 250-line cap. Now that it sits at 100% line
and branch coverage, split along its natural seams:
_types.py 48 the three dataclasses, so nothing imports a sibling
merely to name a type (which would have made a cycle)
_render.py 69 the ANSI palette and the printers
_config_io.py 104 variant discovery, JSONC parsing, reads and writes
_hardware.py 106 _run and the four probes
_plan.py 156 the pure hardware -> proposed-changes functions
_optimize.py 100 the CLI entry: _apply_variant and main
Definitions were moved by line range rather than retyped. A throwaway
ast.dump identity check against HEAD confirms all 26 top-level definitions
are unchanged - formatting-blind, logic-sensitive.
The first attempt raised NameError: name '_BO' is not defined at runtime;
_BO was used by main() but missing from the _render import list. Rather than
fix that one failure alone, an AST scan over all six modules for names used
but not bound confirmed nothing else was missing.
Test imports now name the owning module; no re-export barrels. One patch
target genuinely moved - the guard-rail test patches _config_io.Path, since
that is the namespace resolving the name when _discover_variants is called
directly rather than through main().
Verified: same 98 tests pass with every module at 100% line and branch;
python3 -m python_pkg.vscode_optimizer --dry-run reproduces the same
18-change plan and writes nothing; repo-wide pytest 1846 passed at 100%;
over-cap files 36 -> 35.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
9d12c03ccc
commit
377a352d48
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"title": "Split _optimize.py 498 -> six modules under the cap",
|
||||||
|
"objective": "python_pkg/vscode_optimizer/_optimize.py was the last python file over the 250-line cap at 498 lines. Now that it sits at 100% line and branch coverage, split it along its natural seams into shared types, hardware probes, the pure planning layer, filesystem I/O, terminal rendering, and the CLI entry. Code moves verbatim; the tests predate the seam and are re-pointed, not rewritten.",
|
||||||
|
"acceptance_criteria": [
|
||||||
|
"Every resulting module is under 250 lines; the over-cap total falls 36 -> 35",
|
||||||
|
"All 26 top-level definitions are AST-identical to the pre-split file, proved by a throwaway ast.dump comparison against HEAD",
|
||||||
|
"The same 98 tests pass, with every module at 100% line and branch coverage",
|
||||||
|
"python3 -m python_pkg.vscode_optimizer --dry-run reproduces the same 18-change plan and writes nothing",
|
||||||
|
"Import sites name the owning module - no re-export barrels, no sys.path manipulation",
|
||||||
|
"No suppression of any kind: no noqa, no type: ignore, no lowered threshold"
|
||||||
|
],
|
||||||
|
"out_of_scope": [
|
||||||
|
"Fixing the _VENDOR_KW 'ati' substring bug, still pinned by a strict xfail and reported to the user",
|
||||||
|
"Any behaviour change: this commit is a pure relocation of definitions",
|
||||||
|
"The three transcribe_* files and the 29 over-cap shell scripts"
|
||||||
|
],
|
||||||
|
"verifier": "A throwaway ast.dump identity check against HEAD for the verbatim claim; pytest python_pkg/vscode_optimizer/ --cov --cov-branch for tests and coverage; python3 -m python_pkg.vscode_optimizer --dry-run for the behaviour claim; bash ~/utils/scripts/check_file_length.sh --all for the cap; pre-commit run --files for lint."
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"intent": "Split the 498-line _optimize.py into six modules, each well under the 250-line cap, moving every definition verbatim and leaving behaviour unchanged.",
|
||||||
|
"scope": [
|
||||||
|
"python_pkg/vscode_optimizer/_optimize.py 498 -> 100 (CLI entry: _apply_variant, main)",
|
||||||
|
"New: _types.py 48, _render.py 69, _config_io.py 104, _hardware.py 106, _plan.py 156",
|
||||||
|
"Re-pointed imports and patch targets in the six existing test modules",
|
||||||
|
"Non-goal: any behaviour change, including the known _VENDOR_KW bug"
|
||||||
|
],
|
||||||
|
"changes": [
|
||||||
|
"Extracted the three dataclasses into _types.py so no module imports a sibling merely to name a type, which is what would have created a cycle.",
|
||||||
|
"Extracted _run and the four probes into _hardware.py, the pure planning functions into _plan.py, variant discovery plus JSONC parsing and all file reads/writes into _config_io.py, and the ANSI palette plus the printers into _render.py.",
|
||||||
|
"Definitions were moved with sed over line ranges rather than retyped, so no character could drift; the ast identity check below is what proves it.",
|
||||||
|
"Test imports now name the owning module (from ... import _plan as opt, and so on). No re-export barrels were added.",
|
||||||
|
"One patch target moved: test_main.py's guard-rail test patches _config_io.Path and calls _config_io._discover_variants, because that is the namespace resolving the name once it is called directly rather than through main(). The three patches inside main() correctly still target _optimize, whose namespace resolves them."
|
||||||
|
],
|
||||||
|
"verification": [
|
||||||
|
{
|
||||||
|
"command": "python3 verify_split.py HEAD python_pkg/vscode_optimizer/_optimize.py <six modules>",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "IDENTICAL: all 26 top-level definitions moved verbatim. Each def/class was normalised through ast.dump(ast.parse(ast.unparse(node))) and compared to the pre-split file at HEAD, so the check is formatting-blind but logic-sensitive."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "AST scan for names used but not bound in each of the six modules",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "Zero unbound names. Run after the first split attempt raised NameError: name '_BO' is not defined at _optimize.py:87 - _BO was used by main() but missing from the _render import list. Scanning all six modules at once was what confirmed nothing else was missing, rather than fixing one failure at a time."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pytest python_pkg/vscode_optimizer/ --cov=python_pkg/vscode_optimizer --cov-branch --cov-report=term-missing",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "98 passed, 1 xfailed - the same count as before the split, which is the discriminating check that no test file fell outside the runner's glob. Per-module: _types 26/0, _render 45/0, _plan 56/0, _optimize 58/0, _hardware 65/0, _config_io 66/0 statements missing, all 100% line and branch."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "python3 -m python_pkg.vscode_optimizer --dry-run",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "Same output as the pre-split run: Ryzen 9 7900X3D / RTX 3090 / NVME detected, 18 change(s) proposed, '(dry-run) No files modified.' Every unit test stubs subprocess, so only this real run proves the six-module import graph resolves. Re-run after ruff moved _Hw/_Variant into TYPE_CHECKING, since that rewrite is a known source of runtime-only failures."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "bash ~/utils/scripts/check_file_length.sh --all",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "35 files over cap, down from 36. Largest new module is _plan.py at 156; largest test file is test_main.py at 241. No file in this package is now over the cap."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pre-commit run --files <changed files>",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "All hooks pass, including ruff, ruff format, mypy, pylint, bandit and the pytest coverage gate. Ruff moved two annotation-only imports into TYPE_CHECKING; the suite and the real run were both repeated afterwards."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "pytest (whole repo, memory-capped)",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "1846 passed at 100% total coverage, confirming the split did not disturb the repo-wide gate."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"risks": [
|
||||||
|
"A six-way split multiplies import edges. Mitigated by keeping _types.py dependency-free and _plan.py/_render.py importing nothing from siblings except types, so the graph is a tree rather than a web; the real run confirms it resolves.",
|
||||||
|
"Patch targets that moved could silently stop patching, letting a test call the real function. Mitigated: the suite was run after re-pointing and the count is unchanged at 98, and the one target that genuinely moved failed loudly with AttributeError rather than passing vacuously."
|
||||||
|
],
|
||||||
|
"rollback": [
|
||||||
|
"git revert the commit; _optimize.py returns to its 498-line form and the five new modules disappear.",
|
||||||
|
"After rollback, confirm python3 -m python_pkg.vscode_optimizer --dry-run still prints the 18-change plan and that pytest python_pkg/vscode_optimizer/ is still 98 passed at 100%."
|
||||||
|
]
|
||||||
|
}
|
||||||
104
python_pkg/vscode_optimizer/_config_io.py
Normal file
104
python_pkg/vscode_optimizer/_config_io.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""Find VS Code installations and read and write their configuration.
|
||||||
|
|
||||||
|
Settings are JSON with Comments, which the stdlib cannot parse, so this
|
||||||
|
module carries a small JSONC reader. Every write is preceded by a backup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer._types import _Opt, _Variant
|
||||||
|
|
||||||
|
|
||||||
|
def _discover_variants() -> list[_Variant]:
|
||||||
|
"""Find all installed VS Code variants."""
|
||||||
|
cfg = Path.home() / ".config"
|
||||||
|
cands = [
|
||||||
|
("VS Code (stable)", "Code", "code-flags.conf", "code"),
|
||||||
|
(
|
||||||
|
"VS Code Insiders",
|
||||||
|
"Code - Insiders",
|
||||||
|
"code-insiders-flags.conf",
|
||||||
|
"code-insiders",
|
||||||
|
),
|
||||||
|
("VSCodium", "VSCodium", "vscodium-flags.conf", "codium"),
|
||||||
|
]
|
||||||
|
found: list[_Variant] = []
|
||||||
|
for name, dir_name, flags_name, binary in cands:
|
||||||
|
sp = cfg / dir_name / "User" / "settings.json"
|
||||||
|
fp = cfg / flags_name
|
||||||
|
if sp.exists() or shutil.which(binary):
|
||||||
|
found.append(_Variant(name, sp, fp, binary))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_jsonc(text: str) -> dict[str, object]:
|
||||||
|
"""Parse JSON with Comments (JSONC) used by VS Code."""
|
||||||
|
out: list[str] = []
|
||||||
|
i, n = 0, len(text)
|
||||||
|
while i < n:
|
||||||
|
ch = text[i]
|
||||||
|
if ch == '"':
|
||||||
|
j = i + 1
|
||||||
|
while j < n:
|
||||||
|
if text[j] == "\\":
|
||||||
|
j += 2
|
||||||
|
continue
|
||||||
|
if text[j] == '"':
|
||||||
|
j += 1
|
||||||
|
break
|
||||||
|
j += 1
|
||||||
|
out.append(text[i:j])
|
||||||
|
i = j
|
||||||
|
elif ch == "/" and i + 1 < n and text[i + 1] == "/":
|
||||||
|
while i < n and text[i] != "\n":
|
||||||
|
i += 1
|
||||||
|
elif ch == "/" and i + 1 < n and text[i + 1] == "*":
|
||||||
|
end = text.find("*/", i + 2)
|
||||||
|
i = end + 2 if end != -1 else n
|
||||||
|
else:
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
cleaned = re.sub(r",(\s*[}\]])", r"\1", "".join(out))
|
||||||
|
if not cleaned.strip():
|
||||||
|
return {}
|
||||||
|
parsed: dict[str, object] = json.loads(cleaned)
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _backup(path: Path) -> Path | None:
|
||||||
|
if not path.exists():
|
||||||
|
return None
|
||||||
|
ts = datetime.now(tz=timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||||
|
dst = path.with_suffix(f".{ts}.bak")
|
||||||
|
shutil.copy2(path, dst)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
|
||||||
|
def _read_settings(path: Path) -> dict[str, object]:
|
||||||
|
return _parse_jsonc(path.read_text()) if path.exists() else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _write_settings(path: Path, current: dict[str, object], opts: list[_Opt]) -> None:
|
||||||
|
merged = {**current, **{o.key: o.value for o in opts}}
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(json.dumps(merged, indent=4, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_flags(path: Path) -> list[str]:
|
||||||
|
if not path.exists():
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
ln.strip()
|
||||||
|
for ln in path.read_text().splitlines()
|
||||||
|
if ln.strip() and not ln.strip().startswith("#")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _write_flags(path: Path, flags: list[str]) -> None:
|
||||||
|
path.write_text("\n".join(flags) + "\n")
|
||||||
106
python_pkg/vscode_optimizer/_hardware.py
Normal file
106
python_pkg/vscode_optimizer/_hardware.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""Probe the machine's CPU, RAM, GPU, and root disk.
|
||||||
|
|
||||||
|
Every probe shells out and degrades to a default rather than raising, so a
|
||||||
|
missing tool narrows the report instead of failing the run.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw
|
||||||
|
|
||||||
|
_MIB_1024 = 1024
|
||||||
|
_LSCPU = {
|
||||||
|
"Model name": "cpu_model",
|
||||||
|
"CPU(s)": "cpu_logical_cores",
|
||||||
|
"Core(s) per socket": "cpu_physical_cores",
|
||||||
|
"CPU max MHz": "cpu_max_mhz",
|
||||||
|
}
|
||||||
|
_VENDOR_KW = {"nvidia": "NVIDIA", "amd": "AMD", "ati": "AMD", "intel": "Intel"}
|
||||||
|
|
||||||
|
|
||||||
|
def _run(args: list[str]) -> str:
|
||||||
|
"""Run *args* and return stdout, or ``""`` on failure."""
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
args,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
return ""
|
||||||
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_cpu(hw: _Hw) -> None:
|
||||||
|
for line in _run(["lscpu"]).splitlines():
|
||||||
|
key, _, val = line.partition(":")
|
||||||
|
attr = _LSCPU.get(key.strip())
|
||||||
|
if attr == "cpu_model":
|
||||||
|
hw.cpu_model = val.strip()
|
||||||
|
elif attr == "cpu_max_mhz":
|
||||||
|
hw.cpu_max_mhz = float(val)
|
||||||
|
elif attr is not None:
|
||||||
|
setattr(hw, attr, int(val))
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_ram(hw: _Hw) -> None:
|
||||||
|
try:
|
||||||
|
meminfo = Path("/proc/meminfo").read_text(encoding="utf-8")
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
m = re.search(r"MemTotal:\s+(\d+)\s+kB", meminfo)
|
||||||
|
if m:
|
||||||
|
hw.ram_total_mb = int(m.group(1)) // _MIB_1024
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_gpu(hw: _Hw) -> None:
|
||||||
|
for line in _run(["lspci"]).splitlines():
|
||||||
|
low = line.lower()
|
||||||
|
if "vga" not in low and "3d" not in low:
|
||||||
|
continue
|
||||||
|
hw.gpu_model = line.rsplit(":", maxsplit=1)[-1].strip()
|
||||||
|
for kw, vendor in _VENDOR_KW.items():
|
||||||
|
if kw in low:
|
||||||
|
hw.gpu_vendor = vendor
|
||||||
|
break
|
||||||
|
if hw.gpu_vendor == "NVIDIA":
|
||||||
|
vram = _run(
|
||||||
|
[
|
||||||
|
"nvidia-smi",
|
||||||
|
"--query-gpu=memory.total",
|
||||||
|
"--format=csv,noheader,nounits",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if vram:
|
||||||
|
hw.gpu_vram_mb = int(vram.split("\n")[0].strip())
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_disk(hw: _Hw) -> None:
|
||||||
|
root_dev = _run(["findmnt", "-n", "-o", "SOURCE", "/"])
|
||||||
|
if not root_dev:
|
||||||
|
return
|
||||||
|
base = re.sub(r"p?\d+$", "", Path(root_dev).name)
|
||||||
|
rotational = Path(f"/sys/block/{base}/queue/rotational")
|
||||||
|
if not rotational.exists():
|
||||||
|
return
|
||||||
|
if rotational.read_text(encoding="utf-8").strip() == "1":
|
||||||
|
hw.disk_type = "hdd"
|
||||||
|
elif "nvme" in base:
|
||||||
|
hw.disk_type = "nvme"
|
||||||
|
else:
|
||||||
|
hw.disk_type = "ssd"
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_hardware() -> _Hw:
|
||||||
|
"""Probe CPU, RAM, GPU, and root disk type."""
|
||||||
|
hw = _Hw()
|
||||||
|
for fn in (_detect_cpu, _detect_ram, _detect_gpu, _detect_disk):
|
||||||
|
fn(hw)
|
||||||
|
return hw
|
||||||
445
python_pkg/vscode_optimizer/_optimize.py
Executable file → Normal file
445
python_pkg/vscode_optimizer/_optimize.py
Executable file → Normal file
@ -1,432 +1,37 @@
|
|||||||
#!/usr/bin/env python3
|
"""Auto-optimize VS Code settings based on detected hardware.
|
||||||
"""Auto-optimize VS Code settings based on detected hardware."""
|
|
||||||
|
Run as ``python3 -m python_pkg.vscode_optimizer [--dry-run] [--yes]``.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
_RAM_THRESHOLDS = ((28000, 4096), (14000, 2048), (7000, 1024))
|
from python_pkg.vscode_optimizer._config_io import (
|
||||||
_DEFAULT_MEM = 512
|
_backup,
|
||||||
_MIB_1024 = 1024
|
_discover_variants,
|
||||||
_MIN_THREADS = 4
|
_read_flags,
|
||||||
_SUBMOD_LIMIT = 30
|
_read_settings,
|
||||||
_LSCPU = {
|
_write_flags,
|
||||||
"Model name": "cpu_model",
|
_write_settings,
|
||||||
"CPU(s)": "cpu_logical_cores",
|
|
||||||
"Core(s) per socket": "cpu_physical_cores",
|
|
||||||
"CPU max MHz": "cpu_max_mhz",
|
|
||||||
}
|
|
||||||
_VENDOR_KW = {"nvidia": "NVIDIA", "amd": "AMD", "ati": "AMD", "intel": "Intel"}
|
|
||||||
_WATCHER_EX: dict[str, bool] = dict.fromkeys(
|
|
||||||
[
|
|
||||||
"**/.git/objects/**",
|
|
||||||
"**/.git/subtree-cache/**",
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/.venv/**",
|
|
||||||
"**/venv/**",
|
|
||||||
"**/__pycache__/**",
|
|
||||||
"**/build/**",
|
|
||||||
"**/.mypy_cache/**",
|
|
||||||
"**/.ruff_cache/**",
|
|
||||||
"**/.pytest_cache/**",
|
|
||||||
"**/dist/**",
|
|
||||||
"**/*.egg-info/**",
|
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
)
|
||||||
_SEARCH_EX: dict[str, bool] = dict.fromkeys(
|
from python_pkg.vscode_optimizer._hardware import _detect_hardware
|
||||||
[
|
from python_pkg.vscode_optimizer._plan import _compute_opts, _gpu_flags
|
||||||
"**/node_modules",
|
from python_pkg.vscode_optimizer._render import (
|
||||||
"**/build",
|
_BO,
|
||||||
"**/.venv",
|
_G,
|
||||||
"**/venv",
|
_R,
|
||||||
"**/__pycache__",
|
_Y,
|
||||||
"**/.mypy_cache",
|
_hdr,
|
||||||
"**/.ruff_cache",
|
_out,
|
||||||
"**/.pytest_cache",
|
_show_hw,
|
||||||
"**/dist",
|
_show_plan,
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
)
|
||||||
_B = "\033[94m"
|
|
||||||
_G = "\033[92m"
|
|
||||||
_Y = "\033[93m"
|
|
||||||
_C = "\033[96m"
|
|
||||||
_BO = "\033[1m"
|
|
||||||
_R = "\033[0m"
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
@dataclass
|
from python_pkg.vscode_optimizer._types import _Hw, _Variant
|
||||||
class _Hw:
|
|
||||||
"""Detected system hardware."""
|
|
||||||
|
|
||||||
cpu_model: str = "Unknown"
|
|
||||||
cpu_physical_cores: int = 1
|
|
||||||
cpu_logical_cores: int = 1
|
|
||||||
cpu_max_mhz: float = 0.0
|
|
||||||
ram_total_mb: int = 0
|
|
||||||
gpu_vendor: str = "Unknown"
|
|
||||||
gpu_model: str = "Unknown"
|
|
||||||
gpu_vram_mb: int = 0
|
|
||||||
disk_type: str = "unknown"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _Opt:
|
|
||||||
"""Single proposed change."""
|
|
||||||
|
|
||||||
key: str
|
|
||||||
value: object
|
|
||||||
reason: str
|
|
||||||
current: object = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _Variant:
|
|
||||||
"""A VS Code installation."""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
settings: Path
|
|
||||||
flags: Path
|
|
||||||
binary: str
|
|
||||||
|
|
||||||
|
|
||||||
def _run(args: list[str]) -> str:
|
|
||||||
"""Run *args* and return stdout, or ``""`` on failure."""
|
|
||||||
try:
|
|
||||||
proc = subprocess.run(
|
|
||||||
args,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
||||||
return ""
|
|
||||||
return proc.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_cpu(hw: _Hw) -> None:
|
|
||||||
for line in _run(["lscpu"]).splitlines():
|
|
||||||
key, _, val = line.partition(":")
|
|
||||||
attr = _LSCPU.get(key.strip())
|
|
||||||
if attr == "cpu_model":
|
|
||||||
hw.cpu_model = val.strip()
|
|
||||||
elif attr == "cpu_max_mhz":
|
|
||||||
hw.cpu_max_mhz = float(val)
|
|
||||||
elif attr is not None:
|
|
||||||
setattr(hw, attr, int(val))
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_ram(hw: _Hw) -> None:
|
|
||||||
try:
|
|
||||||
meminfo = Path("/proc/meminfo").read_text(encoding="utf-8")
|
|
||||||
except OSError:
|
|
||||||
return
|
|
||||||
m = re.search(r"MemTotal:\s+(\d+)\s+kB", meminfo)
|
|
||||||
if m:
|
|
||||||
hw.ram_total_mb = int(m.group(1)) // _MIB_1024
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_gpu(hw: _Hw) -> None:
|
|
||||||
for line in _run(["lspci"]).splitlines():
|
|
||||||
low = line.lower()
|
|
||||||
if "vga" not in low and "3d" not in low:
|
|
||||||
continue
|
|
||||||
hw.gpu_model = line.rsplit(":", maxsplit=1)[-1].strip()
|
|
||||||
for kw, vendor in _VENDOR_KW.items():
|
|
||||||
if kw in low:
|
|
||||||
hw.gpu_vendor = vendor
|
|
||||||
break
|
|
||||||
if hw.gpu_vendor == "NVIDIA":
|
|
||||||
vram = _run(
|
|
||||||
[
|
|
||||||
"nvidia-smi",
|
|
||||||
"--query-gpu=memory.total",
|
|
||||||
"--format=csv,noheader,nounits",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if vram:
|
|
||||||
hw.gpu_vram_mb = int(vram.split("\n")[0].strip())
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_disk(hw: _Hw) -> None:
|
|
||||||
root_dev = _run(["findmnt", "-n", "-o", "SOURCE", "/"])
|
|
||||||
if not root_dev:
|
|
||||||
return
|
|
||||||
base = re.sub(r"p?\d+$", "", Path(root_dev).name)
|
|
||||||
rotational = Path(f"/sys/block/{base}/queue/rotational")
|
|
||||||
if not rotational.exists():
|
|
||||||
return
|
|
||||||
if rotational.read_text(encoding="utf-8").strip() == "1":
|
|
||||||
hw.disk_type = "hdd"
|
|
||||||
elif "nvme" in base:
|
|
||||||
hw.disk_type = "nvme"
|
|
||||||
else:
|
|
||||||
hw.disk_type = "ssd"
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_hardware() -> _Hw:
|
|
||||||
"""Probe CPU, RAM, GPU, and root disk type."""
|
|
||||||
hw = _Hw()
|
|
||||||
for fn in (_detect_cpu, _detect_ram, _detect_gpu, _detect_disk):
|
|
||||||
fn(hw)
|
|
||||||
return hw
|
|
||||||
|
|
||||||
|
|
||||||
def _discover_variants() -> list[_Variant]:
|
|
||||||
"""Find all installed VS Code variants."""
|
|
||||||
cfg = Path.home() / ".config"
|
|
||||||
cands = [
|
|
||||||
("VS Code (stable)", "Code", "code-flags.conf", "code"),
|
|
||||||
(
|
|
||||||
"VS Code Insiders",
|
|
||||||
"Code - Insiders",
|
|
||||||
"code-insiders-flags.conf",
|
|
||||||
"code-insiders",
|
|
||||||
),
|
|
||||||
("VSCodium", "VSCodium", "vscodium-flags.conf", "codium"),
|
|
||||||
]
|
|
||||||
found: list[_Variant] = []
|
|
||||||
for name, dir_name, flags_name, binary in cands:
|
|
||||||
sp = cfg / dir_name / "User" / "settings.json"
|
|
||||||
fp = cfg / flags_name
|
|
||||||
if sp.exists() or shutil.which(binary):
|
|
||||||
found.append(_Variant(name, sp, fp, binary))
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_jsonc(text: str) -> dict[str, object]:
|
|
||||||
"""Parse JSON with Comments (JSONC) used by VS Code."""
|
|
||||||
out: list[str] = []
|
|
||||||
i, n = 0, len(text)
|
|
||||||
while i < n:
|
|
||||||
ch = text[i]
|
|
||||||
if ch == '"':
|
|
||||||
j = i + 1
|
|
||||||
while j < n:
|
|
||||||
if text[j] == "\\":
|
|
||||||
j += 2
|
|
||||||
continue
|
|
||||||
if text[j] == '"':
|
|
||||||
j += 1
|
|
||||||
break
|
|
||||||
j += 1
|
|
||||||
out.append(text[i:j])
|
|
||||||
i = j
|
|
||||||
elif ch == "/" and i + 1 < n and text[i + 1] == "/":
|
|
||||||
while i < n and text[i] != "\n":
|
|
||||||
i += 1
|
|
||||||
elif ch == "/" and i + 1 < n and text[i + 1] == "*":
|
|
||||||
end = text.find("*/", i + 2)
|
|
||||||
i = end + 2 if end != -1 else n
|
|
||||||
else:
|
|
||||||
out.append(ch)
|
|
||||||
i += 1
|
|
||||||
cleaned = re.sub(r",(\s*[}\]])", r"\1", "".join(out))
|
|
||||||
if not cleaned.strip():
|
|
||||||
return {}
|
|
||||||
parsed: dict[str, object] = json.loads(cleaned)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def _ideal_mem(ram_mb: int) -> int:
|
|
||||||
for threshold, value in _RAM_THRESHOLDS:
|
|
||||||
if ram_mb >= threshold:
|
|
||||||
return value
|
|
||||||
return _DEFAULT_MEM
|
|
||||||
|
|
||||||
|
|
||||||
def _dict_merge_opt(
|
|
||||||
cur_settings: dict[str, object],
|
|
||||||
key: str,
|
|
||||||
ideal: dict[str, bool],
|
|
||||||
reason: str,
|
|
||||||
) -> _Opt | None:
|
|
||||||
cur = cur_settings.get(key, {})
|
|
||||||
if not isinstance(cur, dict):
|
|
||||||
cur = {}
|
|
||||||
if all(k in cur for k in ideal):
|
|
||||||
return None
|
|
||||||
return _Opt(key, {**cur, **ideal}, reason, cur or None)
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_opts(hw: _Hw, cur: dict[str, object]) -> list[_Opt]:
|
|
||||||
"""Return optimizations based on hardware and current settings."""
|
|
||||||
opts: list[_Opt] = []
|
|
||||||
|
|
||||||
def _p(key: str, val: object, reason: str) -> None:
|
|
||||||
if cur.get(key) != val:
|
|
||||||
opts.append(_Opt(key, val, reason, cur.get(key)))
|
|
||||||
|
|
||||||
threads = max(_MIN_THREADS, hw.cpu_physical_cores)
|
|
||||||
_p(
|
|
||||||
"search.maxThreads",
|
|
||||||
threads,
|
|
||||||
f"{hw.cpu_physical_cores} physical cores - use them for workspace search",
|
|
||||||
)
|
|
||||||
mem = _ideal_mem(hw.ram_total_mb)
|
|
||||||
_p(
|
|
||||||
"files.maxMemoryForLargeFilesMB",
|
|
||||||
mem,
|
|
||||||
f"{hw.ram_total_mb // _MIB_1024} GB RAM - allow up to {mem} MB for large files",
|
|
||||||
)
|
|
||||||
if hw.gpu_vendor in ("NVIDIA", "AMD"):
|
|
||||||
_p(
|
|
||||||
"terminal.integrated.gpuAcceleration",
|
|
||||||
"on",
|
|
||||||
f"{hw.gpu_vendor} GPU - enable GPU-rendered terminal",
|
|
||||||
)
|
|
||||||
smooth = True
|
|
||||||
for key in (
|
|
||||||
"editor.smoothScrolling",
|
|
||||||
"workbench.list.smoothScrolling",
|
|
||||||
"terminal.integrated.smoothScrolling",
|
|
||||||
):
|
|
||||||
_p(key, smooth, "Smooth scrolling is free with a dedicated GPU")
|
|
||||||
no = False
|
|
||||||
_p("search.followSymlinks", no, "Avoid duplicate results and wasted I/O")
|
|
||||||
for result in (
|
|
||||||
_dict_merge_opt(
|
|
||||||
cur,
|
|
||||||
"files.watcherExclude",
|
|
||||||
_WATCHER_EX,
|
|
||||||
"Exclude build/cache dirs from file watcher",
|
|
||||||
),
|
|
||||||
_dict_merge_opt(
|
|
||||||
cur, "search.exclude", _SEARCH_EX, "Exclude build/cache dirs from search"
|
|
||||||
),
|
|
||||||
):
|
|
||||||
if result:
|
|
||||||
opts.extend([result])
|
|
||||||
_p("editor.guides.bracketPairs", "active", "Lightweight visual aid")
|
|
||||||
_p(
|
|
||||||
"diffEditor.maxComputationTime",
|
|
||||||
0,
|
|
||||||
f"Fast CPU ({hw.cpu_model}) - compute diffs fully",
|
|
||||||
)
|
|
||||||
_p("editor.minimap.enabled", no, "Minimap consumes GPU/CPU for little benefit")
|
|
||||||
cur_sub = cur.get("git.detectSubmodulesLimit")
|
|
||||||
if cur_sub is None or (isinstance(cur_sub, int) and cur_sub < _SUBMOD_LIMIT):
|
|
||||||
opts.append(
|
|
||||||
_Opt(
|
|
||||||
"git.detectSubmodulesLimit",
|
|
||||||
_SUBMOD_LIMIT,
|
|
||||||
"Higher limit is fine with fast CPU",
|
|
||||||
cur_sub,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return opts
|
|
||||||
|
|
||||||
|
|
||||||
def _gpu_flags(hw: _Hw) -> list[str]:
|
|
||||||
"""Return Electron flags appropriate for the detected GPU."""
|
|
||||||
if hw.gpu_vendor in ("NVIDIA", "AMD"):
|
|
||||||
base = [
|
|
||||||
"--enable-gpu-rasterization",
|
|
||||||
"--enable-zero-copy",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--enable-features=CanvasOopRasterization",
|
|
||||||
]
|
|
||||||
if hw.gpu_vendor == "NVIDIA":
|
|
||||||
base.append("--enable-features=VaapiVideoDecodeLinuxGL,VaapiVideoEncoder")
|
|
||||||
return base
|
|
||||||
if hw.gpu_vendor == "Intel":
|
|
||||||
return [
|
|
||||||
"--enable-gpu-rasterization",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--enable-features=VaapiVideoDecodeLinuxGL",
|
|
||||||
]
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def _backup(path: Path) -> Path | None:
|
|
||||||
if not path.exists():
|
|
||||||
return None
|
|
||||||
ts = datetime.now(tz=timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
||||||
dst = path.with_suffix(f".{ts}.bak")
|
|
||||||
shutil.copy2(path, dst)
|
|
||||||
return dst
|
|
||||||
|
|
||||||
|
|
||||||
def _read_settings(path: Path) -> dict[str, object]:
|
|
||||||
return _parse_jsonc(path.read_text()) if path.exists() else {}
|
|
||||||
|
|
||||||
|
|
||||||
def _write_settings(path: Path, current: dict[str, object], opts: list[_Opt]) -> None:
|
|
||||||
merged = {**current, **{o.key: o.value for o in opts}}
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_text(json.dumps(merged, indent=4, ensure_ascii=False) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def _read_flags(path: Path) -> list[str]:
|
|
||||||
if not path.exists():
|
|
||||||
return []
|
|
||||||
return [
|
|
||||||
ln.strip()
|
|
||||||
for ln in path.read_text().splitlines()
|
|
||||||
if ln.strip() and not ln.strip().startswith("#")
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _write_flags(path: Path, flags: list[str]) -> None:
|
|
||||||
path.write_text("\n".join(flags) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def _out(text: str = "") -> None:
|
|
||||||
"""Write a line to stdout."""
|
|
||||||
sys.stdout.write(text + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def _hdr(text: str) -> None:
|
|
||||||
_out(f"\n{_BO}{_B}{'─' * 60}{_R}\n{_BO}{_B} {text}{_R}\n{_BO}{_B}{'─' * 60}{_R}")
|
|
||||||
|
|
||||||
|
|
||||||
def _show_hw(hw: _Hw) -> None:
|
|
||||||
_hdr("Detected Hardware")
|
|
||||||
_out(f" {_C}CPU{_R} {hw.cpu_model}")
|
|
||||||
_out(
|
|
||||||
f" {hw.cpu_physical_cores} cores / {hw.cpu_logical_cores} threads"
|
|
||||||
f" @ {hw.cpu_max_mhz:.0f} MHz"
|
|
||||||
)
|
|
||||||
_out(f" {_C}RAM{_R} {hw.ram_total_mb // _MIB_1024} GB")
|
|
||||||
gpu = f" {_C}GPU{_R} {hw.gpu_vendor} - {hw.gpu_model}"
|
|
||||||
if hw.gpu_vram_mb:
|
|
||||||
gpu += f" ({hw.gpu_vram_mb} MB VRAM)"
|
|
||||||
_out(gpu)
|
|
||||||
_out(f" {_C}Disk{_R} {hw.disk_type.upper()}")
|
|
||||||
|
|
||||||
|
|
||||||
def _show_plan(opts: list[_Opt], new_fl: list[str], old_fl: list[str]) -> None:
|
|
||||||
_hdr("Optimization Plan")
|
|
||||||
added = [f for f in new_fl if f not in old_fl]
|
|
||||||
if added:
|
|
||||||
_out(f"\n {_BO}Electron flags to add:{_R}")
|
|
||||||
for fl in added:
|
|
||||||
_out(f" {_G}+ {fl}{_R}")
|
|
||||||
elif new_fl:
|
|
||||||
_out(f"\n {_Y}Electron GPU flags already present{_R}")
|
|
||||||
if opts:
|
|
||||||
_out(f"\n {_BO}Settings to change:{_R}")
|
|
||||||
for i, o in enumerate(opts, 1):
|
|
||||||
c = json.dumps(o.current)[:55] if o.current is not None else "-"
|
|
||||||
v = json.dumps(o.value)[:55]
|
|
||||||
_out(f"\n {_BO}{i}. {o.key}{_R}\n {o.reason}")
|
|
||||||
_out(f" {_Y}{c}{_R} -> {_G}{v}{_R}")
|
|
||||||
else:
|
|
||||||
_out(f"\n {_G}All settings already optimized{_R}")
|
|
||||||
total = len(opts) + len(added)
|
|
||||||
if total:
|
|
||||||
_out(f"\n {_BO}{total} change(s) proposed.{_R}")
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_variant(
|
def _apply_variant(
|
||||||
|
|||||||
156
python_pkg/vscode_optimizer/_plan.py
Normal file
156
python_pkg/vscode_optimizer/_plan.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
"""Turn detected hardware plus current settings into a list of changes.
|
||||||
|
|
||||||
|
Pure functions: nothing here reads the filesystem or shells out. Each
|
||||||
|
proposal carries the value it replaces, so the plan can be shown as a diff.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw, _Opt
|
||||||
|
|
||||||
|
_RAM_THRESHOLDS = ((28000, 4096), (14000, 2048), (7000, 1024))
|
||||||
|
_DEFAULT_MEM = 512
|
||||||
|
_MIB_1024 = 1024
|
||||||
|
_MIN_THREADS = 4
|
||||||
|
_SUBMOD_LIMIT = 30
|
||||||
|
_WATCHER_EX: dict[str, bool] = dict.fromkeys(
|
||||||
|
[
|
||||||
|
"**/.git/objects/**",
|
||||||
|
"**/.git/subtree-cache/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/.venv/**",
|
||||||
|
"**/venv/**",
|
||||||
|
"**/__pycache__/**",
|
||||||
|
"**/build/**",
|
||||||
|
"**/.mypy_cache/**",
|
||||||
|
"**/.ruff_cache/**",
|
||||||
|
"**/.pytest_cache/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/*.egg-info/**",
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
_SEARCH_EX: dict[str, bool] = dict.fromkeys(
|
||||||
|
[
|
||||||
|
"**/node_modules",
|
||||||
|
"**/build",
|
||||||
|
"**/.venv",
|
||||||
|
"**/venv",
|
||||||
|
"**/__pycache__",
|
||||||
|
"**/.mypy_cache",
|
||||||
|
"**/.ruff_cache",
|
||||||
|
"**/.pytest_cache",
|
||||||
|
"**/dist",
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _ideal_mem(ram_mb: int) -> int:
|
||||||
|
for threshold, value in _RAM_THRESHOLDS:
|
||||||
|
if ram_mb >= threshold:
|
||||||
|
return value
|
||||||
|
return _DEFAULT_MEM
|
||||||
|
|
||||||
|
|
||||||
|
def _dict_merge_opt(
|
||||||
|
cur_settings: dict[str, object],
|
||||||
|
key: str,
|
||||||
|
ideal: dict[str, bool],
|
||||||
|
reason: str,
|
||||||
|
) -> _Opt | None:
|
||||||
|
cur = cur_settings.get(key, {})
|
||||||
|
if not isinstance(cur, dict):
|
||||||
|
cur = {}
|
||||||
|
if all(k in cur for k in ideal):
|
||||||
|
return None
|
||||||
|
return _Opt(key, {**cur, **ideal}, reason, cur or None)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_opts(hw: _Hw, cur: dict[str, object]) -> list[_Opt]:
|
||||||
|
"""Return optimizations based on hardware and current settings."""
|
||||||
|
opts: list[_Opt] = []
|
||||||
|
|
||||||
|
def _p(key: str, val: object, reason: str) -> None:
|
||||||
|
if cur.get(key) != val:
|
||||||
|
opts.append(_Opt(key, val, reason, cur.get(key)))
|
||||||
|
|
||||||
|
threads = max(_MIN_THREADS, hw.cpu_physical_cores)
|
||||||
|
_p(
|
||||||
|
"search.maxThreads",
|
||||||
|
threads,
|
||||||
|
f"{hw.cpu_physical_cores} physical cores - use them for workspace search",
|
||||||
|
)
|
||||||
|
mem = _ideal_mem(hw.ram_total_mb)
|
||||||
|
_p(
|
||||||
|
"files.maxMemoryForLargeFilesMB",
|
||||||
|
mem,
|
||||||
|
f"{hw.ram_total_mb // _MIB_1024} GB RAM - allow up to {mem} MB for large files",
|
||||||
|
)
|
||||||
|
if hw.gpu_vendor in ("NVIDIA", "AMD"):
|
||||||
|
_p(
|
||||||
|
"terminal.integrated.gpuAcceleration",
|
||||||
|
"on",
|
||||||
|
f"{hw.gpu_vendor} GPU - enable GPU-rendered terminal",
|
||||||
|
)
|
||||||
|
smooth = True
|
||||||
|
for key in (
|
||||||
|
"editor.smoothScrolling",
|
||||||
|
"workbench.list.smoothScrolling",
|
||||||
|
"terminal.integrated.smoothScrolling",
|
||||||
|
):
|
||||||
|
_p(key, smooth, "Smooth scrolling is free with a dedicated GPU")
|
||||||
|
no = False
|
||||||
|
_p("search.followSymlinks", no, "Avoid duplicate results and wasted I/O")
|
||||||
|
for result in (
|
||||||
|
_dict_merge_opt(
|
||||||
|
cur,
|
||||||
|
"files.watcherExclude",
|
||||||
|
_WATCHER_EX,
|
||||||
|
"Exclude build/cache dirs from file watcher",
|
||||||
|
),
|
||||||
|
_dict_merge_opt(
|
||||||
|
cur, "search.exclude", _SEARCH_EX, "Exclude build/cache dirs from search"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
if result:
|
||||||
|
opts.extend([result])
|
||||||
|
_p("editor.guides.bracketPairs", "active", "Lightweight visual aid")
|
||||||
|
_p(
|
||||||
|
"diffEditor.maxComputationTime",
|
||||||
|
0,
|
||||||
|
f"Fast CPU ({hw.cpu_model}) - compute diffs fully",
|
||||||
|
)
|
||||||
|
_p("editor.minimap.enabled", no, "Minimap consumes GPU/CPU for little benefit")
|
||||||
|
cur_sub = cur.get("git.detectSubmodulesLimit")
|
||||||
|
if cur_sub is None or (isinstance(cur_sub, int) and cur_sub < _SUBMOD_LIMIT):
|
||||||
|
opts.append(
|
||||||
|
_Opt(
|
||||||
|
"git.detectSubmodulesLimit",
|
||||||
|
_SUBMOD_LIMIT,
|
||||||
|
"Higher limit is fine with fast CPU",
|
||||||
|
cur_sub,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
def _gpu_flags(hw: _Hw) -> list[str]:
|
||||||
|
"""Return Electron flags appropriate for the detected GPU."""
|
||||||
|
if hw.gpu_vendor in ("NVIDIA", "AMD"):
|
||||||
|
base = [
|
||||||
|
"--enable-gpu-rasterization",
|
||||||
|
"--enable-zero-copy",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--enable-features=CanvasOopRasterization",
|
||||||
|
]
|
||||||
|
if hw.gpu_vendor == "NVIDIA":
|
||||||
|
base.append("--enable-features=VaapiVideoDecodeLinuxGL,VaapiVideoEncoder")
|
||||||
|
return base
|
||||||
|
if hw.gpu_vendor == "Intel":
|
||||||
|
return [
|
||||||
|
"--enable-gpu-rasterization",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--enable-features=VaapiVideoDecodeLinuxGL",
|
||||||
|
]
|
||||||
|
return []
|
||||||
69
python_pkg/vscode_optimizer/_render.py
Normal file
69
python_pkg/vscode_optimizer/_render.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Render the hardware summary and the optimization plan to the terminal.
|
||||||
|
|
||||||
|
The only module that owns the ANSI colour codes, so changing the palette
|
||||||
|
touches one file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw, _Opt
|
||||||
|
|
||||||
|
_MIB_1024 = 1024
|
||||||
|
_B = "\033[94m"
|
||||||
|
_G = "\033[92m"
|
||||||
|
_Y = "\033[93m"
|
||||||
|
_C = "\033[96m"
|
||||||
|
_BO = "\033[1m"
|
||||||
|
_R = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def _out(text: str = "") -> None:
|
||||||
|
"""Write a line to stdout."""
|
||||||
|
sys.stdout.write(text + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _hdr(text: str) -> None:
|
||||||
|
_out(f"\n{_BO}{_B}{'─' * 60}{_R}\n{_BO}{_B} {text}{_R}\n{_BO}{_B}{'─' * 60}{_R}")
|
||||||
|
|
||||||
|
|
||||||
|
def _show_hw(hw: _Hw) -> None:
|
||||||
|
_hdr("Detected Hardware")
|
||||||
|
_out(f" {_C}CPU{_R} {hw.cpu_model}")
|
||||||
|
_out(
|
||||||
|
f" {hw.cpu_physical_cores} cores / {hw.cpu_logical_cores} threads"
|
||||||
|
f" @ {hw.cpu_max_mhz:.0f} MHz"
|
||||||
|
)
|
||||||
|
_out(f" {_C}RAM{_R} {hw.ram_total_mb // _MIB_1024} GB")
|
||||||
|
gpu = f" {_C}GPU{_R} {hw.gpu_vendor} - {hw.gpu_model}"
|
||||||
|
if hw.gpu_vram_mb:
|
||||||
|
gpu += f" ({hw.gpu_vram_mb} MB VRAM)"
|
||||||
|
_out(gpu)
|
||||||
|
_out(f" {_C}Disk{_R} {hw.disk_type.upper()}")
|
||||||
|
|
||||||
|
|
||||||
|
def _show_plan(opts: list[_Opt], new_fl: list[str], old_fl: list[str]) -> None:
|
||||||
|
_hdr("Optimization Plan")
|
||||||
|
added = [f for f in new_fl if f not in old_fl]
|
||||||
|
if added:
|
||||||
|
_out(f"\n {_BO}Electron flags to add:{_R}")
|
||||||
|
for fl in added:
|
||||||
|
_out(f" {_G}+ {fl}{_R}")
|
||||||
|
elif new_fl:
|
||||||
|
_out(f"\n {_Y}Electron GPU flags already present{_R}")
|
||||||
|
if opts:
|
||||||
|
_out(f"\n {_BO}Settings to change:{_R}")
|
||||||
|
for i, o in enumerate(opts, 1):
|
||||||
|
c = json.dumps(o.current)[:55] if o.current is not None else "-"
|
||||||
|
v = json.dumps(o.value)[:55]
|
||||||
|
_out(f"\n {_BO}{i}. {o.key}{_R}\n {o.reason}")
|
||||||
|
_out(f" {_Y}{c}{_R} -> {_G}{v}{_R}")
|
||||||
|
else:
|
||||||
|
_out(f"\n {_G}All settings already optimized{_R}")
|
||||||
|
total = len(opts) + len(added)
|
||||||
|
if total:
|
||||||
|
_out(f"\n {_BO}{total} change(s) proposed.{_R}")
|
||||||
48
python_pkg/vscode_optimizer/_types.py
Normal file
48
python_pkg/vscode_optimizer/_types.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"""Detected hardware, proposed changes, and the VS Code installations found.
|
||||||
|
|
||||||
|
These three dataclasses are the vocabulary the other modules share, kept
|
||||||
|
apart from them so nothing has to import a sibling just to name a type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _Hw:
|
||||||
|
"""Detected system hardware."""
|
||||||
|
|
||||||
|
cpu_model: str = "Unknown"
|
||||||
|
cpu_physical_cores: int = 1
|
||||||
|
cpu_logical_cores: int = 1
|
||||||
|
cpu_max_mhz: float = 0.0
|
||||||
|
ram_total_mb: int = 0
|
||||||
|
gpu_vendor: str = "Unknown"
|
||||||
|
gpu_model: str = "Unknown"
|
||||||
|
gpu_vram_mb: int = 0
|
||||||
|
disk_type: str = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _Opt:
|
||||||
|
"""Single proposed change."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
value: object
|
||||||
|
reason: str
|
||||||
|
current: object = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _Variant:
|
||||||
|
"""A VS Code installation."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
settings: Path
|
||||||
|
flags: Path
|
||||||
|
binary: str
|
||||||
@ -12,7 +12,8 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _hardware as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw
|
||||||
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -35,7 +36,7 @@ def test_detect_gpu_reads_nvidia_vram(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||||||
"run",
|
"run",
|
||||||
_fake_run({"lspci": _NVIDIA_LINE, "nvidia-smi": "24576\n"}),
|
_fake_run({"lspci": _NVIDIA_LINE, "nvidia-smi": "24576\n"}),
|
||||||
)
|
)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ def test_detect_gpu_tolerates_nvidia_smi_being_absent(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Without nvidia-smi the vendor is still known; VRAM stays unset."""
|
"""Without nvidia-smi the vendor is still known; VRAM stays unset."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": _NVIDIA_LINE}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": _NVIDIA_LINE}))
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ def test_detect_gpu_maps_each_vendor_keyword(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Vendor keywords map to display names; 3D controllers count as GPUs."""
|
"""Vendor keywords map to display names; 3D controllers count as GPUs."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ def test_detect_gpu_should_recognise_an_intel_igpu(
|
|||||||
"""An Intel iGPU ought to be detected as Intel."""
|
"""An Intel iGPU ought to be detected as Intel."""
|
||||||
line = "00:02.0 VGA compatible controller: Intel UHD Graphics 630"
|
line = "00:02.0 VGA compatible controller: Intel UHD Graphics 630"
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lspci": line}))
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ def test_detect_gpu_leaves_defaults_when_lspci_lists_no_display_device(
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
subprocess, "run", _fake_run({"lspci": "00:1f.3 Audio device: Intel\n"})
|
subprocess, "run", _fake_run({"lspci": "00:1f.3 Audio device: Intel\n"})
|
||||||
)
|
)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ def test_detect_gpu_skips_non_display_devices(
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
subprocess, "run", _fake_run({"lspci": listing, "nvidia-smi": "24576"})
|
subprocess, "run", _fake_run({"lspci": listing, "nvidia-smi": "24576"})
|
||||||
)
|
)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_gpu(hw)
|
opt._detect_gpu(hw)
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ def test_detect_disk_classifies_the_root_device(
|
|||||||
"""Partition suffixes are stripped, then rotational/nvme decide the type."""
|
"""Partition suffixes are stripped, then rotational/nvme decide the type."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": device}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": device}))
|
||||||
_disk_paths(monkeypatch, tmp_path, base, rotational)
|
_disk_paths(monkeypatch, tmp_path, base, rotational)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_disk(hw)
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ def test_detect_disk_gives_up_when_findmnt_says_nothing(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""No root device means the type stays unknown."""
|
"""No root device means the type stays unknown."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({}))
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_disk(hw)
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
@ -204,7 +205,7 @@ def test_detect_disk_gives_up_without_a_rotational_flag(
|
|||||||
"""A device with no sysfs queue entry (e.g. dm-crypt) stays unknown."""
|
"""A device with no sysfs queue entry (e.g. dm-crypt) stays unknown."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": "/dev/mapper/root"}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"findmnt": "/dev/mapper/root"}))
|
||||||
_disk_paths(monkeypatch, tmp_path, "mapper/root", None)
|
_disk_paths(monkeypatch, tmp_path, "mapper/root", None)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_disk(hw)
|
opt._detect_disk(hw)
|
||||||
|
|
||||||
@ -218,10 +219,10 @@ def test_detect_hardware_runs_every_probe(monkeypatch: pytest.MonkeyPatch) -> No
|
|||||||
"""The aggregate calls all four detectors against one _Hw."""
|
"""The aggregate calls all four detectors against one _Hw."""
|
||||||
called: list[str] = []
|
called: list[str] = []
|
||||||
|
|
||||||
def _recorder(name: str) -> Callable[[opt._Hw], None]:
|
def _recorder(name: str) -> Callable[[_Hw], None]:
|
||||||
"""Build a probe double that records the name it stands in for."""
|
"""Build a probe double that records the name it stands in for."""
|
||||||
|
|
||||||
def _probe(_hw: opt._Hw) -> None:
|
def _probe(_hw: _Hw) -> None:
|
||||||
called.append(name)
|
called.append(name)
|
||||||
|
|
||||||
return _probe
|
return _probe
|
||||||
@ -231,5 +232,5 @@ def test_detect_hardware_runs_every_probe(monkeypatch: pytest.MonkeyPatch) -> No
|
|||||||
|
|
||||||
result = opt._detect_hardware()
|
result = opt._detect_hardware()
|
||||||
|
|
||||||
assert isinstance(result, opt._Hw)
|
assert isinstance(result, _Hw)
|
||||||
assert called == ["_detect_cpu", "_detect_ram", "_detect_gpu", "_detect_disk"]
|
assert called == ["_detect_cpu", "_detect_ram", "_detect_gpu", "_detect_disk"]
|
||||||
|
|||||||
@ -12,7 +12,8 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _hardware as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw
|
||||||
from python_pkg.vscode_optimizer.tests.conftest import FakeProc as _Proc
|
from python_pkg.vscode_optimizer.tests.conftest import FakeProc as _Proc
|
||||||
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
from python_pkg.vscode_optimizer.tests.conftest import fake_run as _fake_run
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ def test_detect_cpu_reads_model_cores_and_clock(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Each recognised lscpu key lands on its typed _Hw attribute."""
|
"""Each recognised lscpu key lands on its typed _Hw attribute."""
|
||||||
monkeypatch.setattr(subprocess, "run", _fake_run({"lscpu": _LSCPU_OUT}))
|
monkeypatch.setattr(subprocess, "run", _fake_run({"lscpu": _LSCPU_OUT}))
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_cpu(hw)
|
opt._detect_cpu(hw)
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ def test_detect_cpu_ignores_unknown_keys(monkeypatch: pytest.MonkeyPatch) -> Non
|
|||||||
"run",
|
"run",
|
||||||
_fake_run({"lscpu": "Architecture: x86_64\nBogoMIPS: 9000\n"}),
|
_fake_run({"lscpu": "Architecture: x86_64\nBogoMIPS: 9000\n"}),
|
||||||
)
|
)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_cpu(hw)
|
opt._detect_cpu(hw)
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ def test_detect_ram_converts_kb_to_mb(
|
|||||||
meminfo = tmp_path / "meminfo"
|
meminfo = tmp_path / "meminfo"
|
||||||
meminfo.write_text("MemTotal: 32768000 kB\nMemFree: 100 kB\n")
|
meminfo.write_text("MemTotal: 32768000 kB\nMemFree: 100 kB\n")
|
||||||
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_ram(hw)
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ def test_detect_ram_leaves_zero_when_memtotal_is_absent(
|
|||||||
meminfo = tmp_path / "meminfo"
|
meminfo = tmp_path / "meminfo"
|
||||||
meminfo.write_text("SwapTotal: 4096 kB\n")
|
meminfo.write_text("SwapTotal: 4096 kB\n")
|
||||||
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
monkeypatch.setattr(opt, "Path", lambda _p: meminfo)
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_ram(hw)
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ def test_detect_ram_survives_an_unreadable_procfs(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""An OSError reading /proc/meminfo leaves the default in place."""
|
"""An OSError reading /proc/meminfo leaves the default in place."""
|
||||||
monkeypatch.setattr(opt, "Path", lambda _p: tmp_path / "does-not-exist")
|
monkeypatch.setattr(opt, "Path", lambda _p: tmp_path / "does-not-exist")
|
||||||
hw = opt._Hw()
|
hw = _Hw()
|
||||||
|
|
||||||
opt._detect_ram(hw)
|
opt._detect_ram(hw)
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,8 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _config_io as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Opt
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -183,8 +184,8 @@ def test_write_settings_merges_the_plan_over_the_current_values(
|
|||||||
path = tmp_path / "nested" / "settings.json"
|
path = tmp_path / "nested" / "settings.json"
|
||||||
current: dict[str, object] = {"editor.fontSize": 14, "a": 1}
|
current: dict[str, object] = {"editor.fontSize": 14, "a": 1}
|
||||||
opts = [
|
opts = [
|
||||||
opt._Opt("a", 2, "reason"),
|
_Opt("a", 2, "reason"),
|
||||||
opt._Opt(key="b", value=True, reason="reason"),
|
_Opt(key="b", value=True, reason="reason"),
|
||||||
]
|
]
|
||||||
|
|
||||||
opt._write_settings(path, current, opts)
|
opt._write_settings(path, current, opts)
|
||||||
|
|||||||
@ -14,15 +14,17 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.vscode_optimizer import _config_io as config_io
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _optimize as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw, _Variant
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def _variant(tmp_path: Path) -> opt._Variant:
|
def _variant(tmp_path: Path) -> _Variant:
|
||||||
"""A VS Code variant rooted entirely inside tmp_path."""
|
"""A VS Code variant rooted entirely inside tmp_path."""
|
||||||
return opt._Variant(
|
return _Variant(
|
||||||
name="Test Code",
|
name="Test Code",
|
||||||
settings=tmp_path / "User" / "settings.json",
|
settings=tmp_path / "User" / "settings.json",
|
||||||
flags=tmp_path / "code-flags.conf",
|
flags=tmp_path / "code-flags.conf",
|
||||||
@ -30,7 +32,7 @@ def _variant(tmp_path: Path) -> opt._Variant:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_HW = opt._Hw(cpu_physical_cores=8, ram_total_mb=32000, gpu_vendor="NVIDIA")
|
_HW = _Hw(cpu_physical_cores=8, ram_total_mb=32000, gpu_vendor="NVIDIA")
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
@ -232,8 +234,8 @@ def test_the_suite_never_touches_the_real_user_settings(
|
|||||||
A regression here would mean some test could rewrite the developer's own
|
A regression here would mean some test could rewrite the developer's own
|
||||||
VS Code configuration, which is exactly what --dry-run exists to prevent.
|
VS Code configuration, which is exactly what --dry-run exists to prevent.
|
||||||
"""
|
"""
|
||||||
monkeypatch.setattr(opt.Path, "home", classmethod(lambda _cls: tmp_path))
|
monkeypatch.setattr(config_io.Path, "home", classmethod(lambda _cls: tmp_path))
|
||||||
|
|
||||||
for variant in opt._discover_variants():
|
for variant in config_io._discover_variants():
|
||||||
assert tmp_path in variant.settings.parents
|
assert tmp_path in variant.settings.parents
|
||||||
assert tmp_path in variant.flags.parents
|
assert tmp_path in variant.flags.parents
|
||||||
|
|||||||
@ -9,10 +9,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _plan as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw, _Opt
|
||||||
|
|
||||||
|
|
||||||
def _opts_by_key(opts: list[opt._Opt]) -> dict[str, object]:
|
def _opts_by_key(opts: list[_Opt]) -> dict[str, object]:
|
||||||
"""Index a plan by setting key, for assertions that ignore ordering."""
|
"""Index a plan by setting key, for assertions that ignore ordering."""
|
||||||
return {o.key: o.value for o in opts}
|
return {o.key: o.value for o in opts}
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ def test_dict_merge_opt_reports_no_current_value_when_the_key_is_absent() -> Non
|
|||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
def test_compute_opts_scales_threads_and_memory_to_the_hardware() -> None:
|
def test_compute_opts_scales_threads_and_memory_to_the_hardware() -> None:
|
||||||
"""Physical cores drive search threads; RAM drives the large-file budget."""
|
"""Physical cores drive search threads; RAM drives the large-file budget."""
|
||||||
hw = opt._Hw(cpu_physical_cores=12, ram_total_mb=32000)
|
hw = _Hw(cpu_physical_cores=12, ram_total_mb=32000)
|
||||||
|
|
||||||
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ def test_compute_opts_scales_threads_and_memory_to_the_hardware() -> None:
|
|||||||
|
|
||||||
def test_compute_opts_keeps_a_floor_under_search_threads() -> None:
|
def test_compute_opts_keeps_a_floor_under_search_threads() -> None:
|
||||||
"""A dual-core machine still gets the four-thread minimum."""
|
"""A dual-core machine still gets the four-thread minimum."""
|
||||||
hw = opt._Hw(cpu_physical_cores=2)
|
hw = _Hw(cpu_physical_cores=2)
|
||||||
|
|
||||||
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ def test_compute_opts_keeps_a_floor_under_search_threads() -> None:
|
|||||||
|
|
||||||
def test_compute_opts_enables_gpu_extras_for_a_dedicated_card() -> None:
|
def test_compute_opts_enables_gpu_extras_for_a_dedicated_card() -> None:
|
||||||
"""A discrete GPU turns on terminal acceleration and smooth scrolling."""
|
"""A discrete GPU turns on terminal acceleration and smooth scrolling."""
|
||||||
hw = opt._Hw(gpu_vendor="NVIDIA")
|
hw = _Hw(gpu_vendor="NVIDIA")
|
||||||
|
|
||||||
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
plan = _opts_by_key(opt._compute_opts(hw, {}))
|
||||||
|
|
||||||
@ -122,7 +123,7 @@ def test_compute_opts_enables_gpu_extras_for_a_dedicated_card() -> None:
|
|||||||
|
|
||||||
def test_compute_opts_leaves_gpu_extras_alone_without_a_dedicated_card() -> None:
|
def test_compute_opts_leaves_gpu_extras_alone_without_a_dedicated_card() -> None:
|
||||||
"""Integrated or unknown graphics get no GPU-dependent settings."""
|
"""Integrated or unknown graphics get no GPU-dependent settings."""
|
||||||
plan = _opts_by_key(opt._compute_opts(opt._Hw(gpu_vendor="Intel"), {}))
|
plan = _opts_by_key(opt._compute_opts(_Hw(gpu_vendor="Intel"), {}))
|
||||||
|
|
||||||
assert "terminal.integrated.gpuAcceleration" not in plan
|
assert "terminal.integrated.gpuAcceleration" not in plan
|
||||||
assert "editor.smoothScrolling" not in plan
|
assert "editor.smoothScrolling" not in plan
|
||||||
@ -130,7 +131,7 @@ def test_compute_opts_leaves_gpu_extras_alone_without_a_dedicated_card() -> None
|
|||||||
|
|
||||||
def test_compute_opts_proposes_nothing_already_set() -> None:
|
def test_compute_opts_proposes_nothing_already_set() -> None:
|
||||||
"""Settings that already hold the ideal value are not re-proposed."""
|
"""Settings that already hold the ideal value are not re-proposed."""
|
||||||
hw = opt._Hw(cpu_physical_cores=8, ram_total_mb=32000)
|
hw = _Hw(cpu_physical_cores=8, ram_total_mb=32000)
|
||||||
current: dict[str, object] = {
|
current: dict[str, object] = {
|
||||||
"search.maxThreads": 8,
|
"search.maxThreads": 8,
|
||||||
"files.maxMemoryForLargeFilesMB": 4096,
|
"files.maxMemoryForLargeFilesMB": 4096,
|
||||||
@ -164,7 +165,7 @@ def test_compute_opts_raises_only_a_low_submodule_limit(
|
|||||||
{} if current_limit is None else {"git.detectSubmodulesLimit": current_limit}
|
{} if current_limit is None else {"git.detectSubmodulesLimit": current_limit}
|
||||||
)
|
)
|
||||||
|
|
||||||
plan = _opts_by_key(opt._compute_opts(opt._Hw(), current))
|
plan = _opts_by_key(opt._compute_opts(_Hw(), current))
|
||||||
|
|
||||||
assert ("git.detectSubmodulesLimit" in plan) is proposed
|
assert ("git.detectSubmodulesLimit" in plan) is proposed
|
||||||
|
|
||||||
@ -175,7 +176,7 @@ def test_compute_opts_records_the_previous_value_for_display() -> None:
|
|||||||
|
|
||||||
minimap = next(
|
minimap = next(
|
||||||
o
|
o
|
||||||
for o in opt._compute_opts(opt._Hw(), current)
|
for o in opt._compute_opts(_Hw(), current)
|
||||||
if o.key == "editor.minimap.enabled"
|
if o.key == "editor.minimap.enabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -188,8 +189,8 @@ def test_compute_opts_records_the_previous_value_for_display() -> None:
|
|||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
def test_gpu_flags_adds_a_video_decode_flag_only_for_nvidia() -> None:
|
def test_gpu_flags_adds_a_video_decode_flag_only_for_nvidia() -> None:
|
||||||
"""NVIDIA gets the VAAPI extras on top of the shared rasterization flags."""
|
"""NVIDIA gets the VAAPI extras on top of the shared rasterization flags."""
|
||||||
nvidia = opt._gpu_flags(opt._Hw(gpu_vendor="NVIDIA"))
|
nvidia = opt._gpu_flags(_Hw(gpu_vendor="NVIDIA"))
|
||||||
amd = opt._gpu_flags(opt._Hw(gpu_vendor="AMD"))
|
amd = opt._gpu_flags(_Hw(gpu_vendor="AMD"))
|
||||||
|
|
||||||
assert "--enable-gpu-rasterization" in nvidia
|
assert "--enable-gpu-rasterization" in nvidia
|
||||||
assert "--enable-zero-copy" in nvidia
|
assert "--enable-zero-copy" in nvidia
|
||||||
@ -199,7 +200,7 @@ def test_gpu_flags_adds_a_video_decode_flag_only_for_nvidia() -> None:
|
|||||||
|
|
||||||
def test_gpu_flags_gives_intel_a_reduced_set() -> None:
|
def test_gpu_flags_gives_intel_a_reduced_set() -> None:
|
||||||
"""Intel gets rasterization and decode, but not zero-copy."""
|
"""Intel gets rasterization and decode, but not zero-copy."""
|
||||||
flags = opt._gpu_flags(opt._Hw(gpu_vendor="Intel"))
|
flags = opt._gpu_flags(_Hw(gpu_vendor="Intel"))
|
||||||
|
|
||||||
assert "--enable-gpu-rasterization" in flags
|
assert "--enable-gpu-rasterization" in flags
|
||||||
assert "--enable-zero-copy" not in flags
|
assert "--enable-zero-copy" not in flags
|
||||||
@ -207,4 +208,4 @@ def test_gpu_flags_gives_intel_a_reduced_set() -> None:
|
|||||||
|
|
||||||
def test_gpu_flags_are_empty_for_an_unknown_gpu() -> None:
|
def test_gpu_flags_are_empty_for_an_unknown_gpu() -> None:
|
||||||
"""No flags are forced on hardware the script could not identify."""
|
"""No flags are forced on hardware the script could not identify."""
|
||||||
assert opt._gpu_flags(opt._Hw()) == []
|
assert opt._gpu_flags(_Hw()) == []
|
||||||
|
|||||||
@ -8,7 +8,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from python_pkg.vscode_optimizer import _optimize as opt
|
from python_pkg.vscode_optimizer import _render as opt
|
||||||
|
from python_pkg.vscode_optimizer._types import _Hw, _Opt
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
@ -18,7 +19,7 @@ def test_show_hw_reports_every_detected_component(
|
|||||||
capsys: pytest.CaptureFixture[str],
|
capsys: pytest.CaptureFixture[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""CPU, RAM, GPU and disk each appear, with RAM converted to GB."""
|
"""CPU, RAM, GPU and disk each appear, with RAM converted to GB."""
|
||||||
hw = opt._Hw(
|
hw = _Hw(
|
||||||
cpu_model="AMD Ryzen 9 7900X3D",
|
cpu_model="AMD Ryzen 9 7900X3D",
|
||||||
cpu_physical_cores=12,
|
cpu_physical_cores=12,
|
||||||
cpu_logical_cores=24,
|
cpu_logical_cores=24,
|
||||||
@ -46,7 +47,7 @@ def test_show_hw_omits_vram_when_it_is_unknown(
|
|||||||
capsys: pytest.CaptureFixture[str],
|
capsys: pytest.CaptureFixture[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""A card whose VRAM could not be read shows no empty parentheses."""
|
"""A card whose VRAM could not be read shows no empty parentheses."""
|
||||||
opt._show_hw(opt._Hw(gpu_vendor="AMD", gpu_model="Radeon"))
|
opt._show_hw(_Hw(gpu_vendor="AMD", gpu_model="Radeon"))
|
||||||
|
|
||||||
assert "VRAM" not in capsys.readouterr().out
|
assert "VRAM" not in capsys.readouterr().out
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ def test_show_plan_lists_new_flags_and_settings(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Each proposed change is numbered and shows before -> after."""
|
"""Each proposed change is numbered and shows before -> after."""
|
||||||
opts = [
|
opts = [
|
||||||
opt._Opt(
|
_Opt(
|
||||||
key="editor.minimap.enabled",
|
key="editor.minimap.enabled",
|
||||||
value=False,
|
value=False,
|
||||||
reason="Minimap costs GPU",
|
reason="Minimap costs GPU",
|
||||||
@ -101,7 +102,7 @@ def test_show_plan_renders_an_addition_with_no_previous_value(
|
|||||||
capsys: pytest.CaptureFixture[str],
|
capsys: pytest.CaptureFixture[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""A setting that was never set shows a dash as its 'before'."""
|
"""A setting that was never set shows a dash as its 'before'."""
|
||||||
opt._show_plan([opt._Opt("search.maxThreads", 12, "cores")], [], [])
|
opt._show_plan([_Opt("search.maxThreads", 12, "cores")], [], [])
|
||||||
|
|
||||||
assert "-" in capsys.readouterr().out
|
assert "-" in capsys.readouterr().out
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ def test_show_plan_truncates_very_long_values(
|
|||||||
"""A large exclude dict is clipped so one setting cannot flood the screen."""
|
"""A large exclude dict is clipped so one setting cannot flood the screen."""
|
||||||
big = {f"**/dir{i}": True for i in range(50)}
|
big = {f"**/dir{i}": True for i in range(50)}
|
||||||
|
|
||||||
opt._show_plan([opt._Opt("files.watcherExclude", big, "exclude", big)], [], [])
|
opt._show_plan([_Opt("files.watcherExclude", big, "exclude", big)], [], [])
|
||||||
|
|
||||||
for line in capsys.readouterr().out.splitlines():
|
for line in capsys.readouterr().out.splitlines():
|
||||||
assert len(line) < 200
|
assert len(line) < 200
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user