Skip to content

Commit 14b50f6

Browse files
committed
Initial CPUOpt-Kernel scaffold
0 parents  commit 14b50f6

92 files changed

Lines changed: 1597 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__/
2+
*.pyc
3+
.test-state/

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# CPUOpt-Kernel
2+
3+
CPUOpt-Kernel translates vendor CPU performance controls into safe Linux policy profiles.
4+
It prefers existing kernel interfaces over raw register writes and treats thermal and fan
5+
control as platform concerns, not as a shortcut for unsafe CPU tuning.
6+
7+
Version `v0.1` is intentionally conservative:
8+
9+
- Intel-first, user-space-first implementation
10+
- No raw MSR writes
11+
- No thermal protection bypass
12+
- No voltage or overclock manipulation
13+
- Reversible sysfs writes with snapshot/restore
14+
- Dry-run support for every profile application
15+
16+
## Why this exists
17+
18+
Linux CPU performance policy is spread across multiple interfaces such as `cpufreq`,
19+
`intel_pstate`, `amd-pstate`, EPP/EPB, cpuidle, thermal zones, cooling devices, and
20+
platform-specific hwmon hooks. CPUOpt-Kernel unifies those surfaces into profile-driven,
21+
workload-aware policy choices without pretending every platform exposes the same knobs.
22+
23+
Fan control is platform-specific and is not treated as a CPU register. Performance mode
24+
optimizes for sustained boost and reduced throttling, not unsafe thermal bypass. Quiet mode
25+
reduces CPU aggressiveness before considering fan changes.
26+
27+
## Intel MVP scope
28+
29+
The first release focuses on safe Intel profile application through sysfs:
30+
31+
- `discover` and `status` report CPUFreq, `intel_pstate`, EPP, EPB, turbo, cpuidle,
32+
thermal, cooling, hwmon, SMT, topology, and NUMA metadata when present
33+
- `profile` maps high-level policies to existing sysfs knobs
34+
- `monitor` provides lightweight live telemetry
35+
- `restore` replays the last reversible snapshot
36+
37+
AMD and ARM support are scaffolded for future milestones but remain discovery-first.
38+
39+
## Repository layout
40+
41+
```text
42+
cpuopt-kernel/
43+
cpuoptctl/
44+
docs/
45+
examples/
46+
kernel/
47+
scripts/
48+
tests/
49+
```
50+
51+
## Commands
52+
53+
```bash
54+
python3 cpuoptctl/cpuoptctl.py status
55+
python3 cpuoptctl/cpuoptctl.py discover --json
56+
sudo python3 cpuoptctl/cpuoptctl.py profile performance --dry-run
57+
sudo python3 cpuoptctl/cpuoptctl.py profile balanced
58+
sudo python3 cpuoptctl/cpuoptctl.py profile latency --allow-idle-tuning
59+
sudo python3 cpuoptctl/cpuoptctl.py profile quiet
60+
sudo python3 cpuoptctl/cpuoptctl.py profile ai-inference
61+
sudo python3 cpuoptctl/cpuoptctl.py monitor --interval 2
62+
sudo python3 cpuoptctl/cpuoptctl.py restore
63+
python3 cpuoptctl/cpuoptctl.py export-json
64+
```
65+
66+
For fixture-backed tests:
67+
68+
```bash
69+
python3 cpuoptctl/cpuoptctl.py status --sysfs-root tests/fixtures/intel_hwp
70+
python3 cpuoptctl/cpuoptctl.py profile performance --dry-run --sysfs-root tests/fixtures/intel_hwp
71+
python3 -m unittest
72+
```
73+
74+
## Safety model
75+
76+
- CPUOpt prefers existing kernel interfaces over raw register writes.
77+
- Every write validates path existence, writability intent, and candidate value selection.
78+
- Unknown sysfs paths are skipped, not guessed.
79+
- Failed writes are logged and are non-fatal.
80+
- Modified values are snapshotted to `last_state.json` before application.
81+
- v0.1 never writes `/dev/cpu/*/msr`, firmware registers, BIOS settings, or BMC controls.
82+
- Fan writes are intentionally unimplemented in v0.1 even if fan inspection is enabled.
83+
84+
See [docs/SAFETY.md](C:\Users\ManishKL\Documents\Playground\cpuopt-kernel\docs\SAFETY.md) for the full model.
85+
86+
## Example status output
87+
88+
```text
89+
## CPUOpt Status
90+
91+
Vendor: GenuineIntel
92+
Model: Intel(R) Core(TM) i7-1280P
93+
Kernel: Linux 6.8.0-test
94+
Scaling driver: intel_pstate
95+
HWP/EPP exposed: yes
96+
Turbo control: intel_pstate/no_turbo
97+
Policies:
98+
policy0 CPUs=0-7 governor=powersave min=400000 max=4700000 epp=balance_performance
99+
Thermals:
100+
x86_pkg_temp temp=51000
101+
Warnings:
102+
Fan control is platform-specific; not directly controlled unless safely exposed.
103+
```
104+
105+
## Roadmap highlights
106+
107+
- AMD `amd-pstate` profile support
108+
- ARM SCMI and heterogeneous topology handling
109+
- Optional kernel module under `/sys/kernel/cpuopt/`
110+
- Read-only MSR decoding with model-aware guardrails
111+
- Model-specific write allowlists only after documentation and fallback validation

cpuoptctl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""CPUOpt-Kernel user-space controller package."""

cpuoptctl/cpuopt_amd.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
6+
def annotate(discovery: dict[str, Any]) -> dict[str, Any]:
7+
discovery.setdefault("vendor_notes", []).append("AMD profile writes are not implemented in v0.1.")
8+
return discovery

cpuoptctl/cpuopt_arm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
6+
def annotate(discovery: dict[str, Any]) -> dict[str, Any]:
7+
discovery.setdefault("vendor_notes", []).append("ARM policy writes are not implemented in v0.1.")
8+
return discovery

cpuoptctl/cpuopt_discovery.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from __future__ import annotations
2+
3+
import platform
4+
from pathlib import Path
5+
from typing import Any
6+
7+
try:
8+
from .cpuopt_amd import annotate as annotate_amd
9+
from .cpuopt_arm import annotate as annotate_arm
10+
from .cpuopt_thermal import discover_hwmon, discover_thermal
11+
from .cpuopt_utils import first_existing, list_dirs, normalize_whitespace, read_int, read_text
12+
except ImportError:
13+
from cpuopt_amd import annotate as annotate_amd
14+
from cpuopt_arm import annotate as annotate_arm
15+
from cpuopt_thermal import discover_hwmon, discover_thermal
16+
from cpuopt_utils import first_existing, list_dirs, normalize_whitespace, read_int, read_text
17+
18+
19+
def _parse_cpuinfo(cpuinfo_path: Path) -> dict[str, str]:
20+
parsed: dict[str, str] = {}
21+
text = read_text(cpuinfo_path)
22+
if not text:
23+
return parsed
24+
for line in text.splitlines():
25+
if ":" not in line:
26+
continue
27+
key, value = line.split(":", 1)
28+
key = key.strip()
29+
if key not in parsed:
30+
parsed[key] = value.strip()
31+
return parsed
32+
33+
34+
def _detect_cpuinfo(sysfs_root: Path) -> dict[str, str]:
35+
return _parse_cpuinfo(
36+
first_existing(
37+
[
38+
sysfs_root / "proc" / "cpuinfo",
39+
sysfs_root.parent / "proc" / "cpuinfo",
40+
Path("/proc/cpuinfo"),
41+
]
42+
)
43+
or Path("/proc/cpuinfo")
44+
)
45+
46+
47+
def _detect_kernel_release(sysfs_root: Path) -> str:
48+
value = read_text(
49+
first_existing(
50+
[
51+
sysfs_root / "proc" / "sys" / "kernel" / "osrelease",
52+
sysfs_root.parent / "proc" / "sys" / "kernel" / "osrelease",
53+
Path("/proc/sys/kernel/osrelease"),
54+
]
55+
)
56+
or Path("/proc/sys/kernel/osrelease")
57+
)
58+
return value or platform.release()
59+
60+
61+
def _policy_data(policy: Path) -> dict[str, Any]:
62+
available_governors = (read_text(policy / "scaling_available_governors") or "").split()
63+
available_freqs = (read_text(policy / "scaling_available_frequencies") or "").split()
64+
available_epp = (read_text(policy / "energy_performance_available_preferences") or "").split()
65+
return {
66+
"name": policy.name,
67+
"path": str(policy.resolve()),
68+
"related_cpus": read_text(policy / "related_cpus"),
69+
"scaling_driver": read_text(policy / "scaling_driver"),
70+
"scaling_governor": read_text(policy / "scaling_governor"),
71+
"available_governors": available_governors,
72+
"available_frequencies": available_freqs,
73+
"cpuinfo_min_freq": read_int(policy / "cpuinfo_min_freq"),
74+
"cpuinfo_max_freq": read_int(policy / "cpuinfo_max_freq"),
75+
"scaling_min_freq": read_int(policy / "scaling_min_freq"),
76+
"scaling_max_freq": read_int(policy / "scaling_max_freq"),
77+
"scaling_cur_freq": read_int(policy / "scaling_cur_freq"),
78+
"energy_performance_preference": read_text(policy / "energy_performance_preference"),
79+
"energy_performance_available_preferences": available_epp,
80+
"energy_perf_bias": read_text(policy / "energy_perf_bias"),
81+
}
82+
83+
84+
def _cpuidle_data(sysfs_root: Path) -> list[dict[str, Any]]:
85+
cpu_root = sysfs_root / "devices" / "system" / "cpu"
86+
states: list[dict[str, Any]] = []
87+
for cpu in list_dirs(cpu_root, "cpu[0-9]*"):
88+
for state in list_dirs(cpu / "cpuidle", "state*"):
89+
states.append(
90+
{
91+
"cpu": cpu.name,
92+
"state": state.name,
93+
"name": read_text(state / "name"),
94+
"desc": read_text(state / "desc"),
95+
"latency": read_int(state / "latency"),
96+
"disable": read_text(state / "disable"),
97+
}
98+
)
99+
return states
100+
101+
102+
def _topology_data(sysfs_root: Path) -> list[dict[str, Any]]:
103+
cpu_root = sysfs_root / "devices" / "system" / "cpu"
104+
items: list[dict[str, Any]] = []
105+
for cpu in list_dirs(cpu_root, "cpu[0-9]*"):
106+
topo = cpu / "topology"
107+
items.append(
108+
{
109+
"cpu": cpu.name,
110+
"core_id": read_text(topo / "core_id"),
111+
"physical_package_id": read_text(topo / "physical_package_id"),
112+
"thread_siblings_list": read_text(topo / "thread_siblings_list"),
113+
}
114+
)
115+
return items
116+
117+
118+
def _numa_data(sysfs_root: Path) -> list[dict[str, Any]]:
119+
node_root = sysfs_root / "devices" / "system" / "node"
120+
items: list[dict[str, Any]] = []
121+
for node in list_dirs(node_root, "node*"):
122+
items.append(
123+
{
124+
"name": node.name,
125+
"cpulist": read_text(node / "cpulist"),
126+
"distance": read_text(node / "distance"),
127+
}
128+
)
129+
return items
130+
131+
132+
def discover(sysfs_root: str = "/sys") -> dict[str, Any]:
133+
root = Path(sysfs_root)
134+
cpuinfo = _detect_cpuinfo(root)
135+
cpu_root = root / "devices" / "system" / "cpu"
136+
cpufreq_root = cpu_root / "cpufreq"
137+
intel_pstate_root = cpu_root / "intel_pstate"
138+
amd_pstate_root = cpu_root / "amd_pstate"
139+
140+
policies = [_policy_data(policy) for policy in list_dirs(cpufreq_root, "policy*")]
141+
thermal = discover_thermal(root)
142+
hwmon = discover_hwmon(root)
143+
144+
vulnerabilities: dict[str, str] = {}
145+
vuln_root = cpu_root / "vulnerabilities"
146+
if vuln_root.exists():
147+
for candidate in sorted(vuln_root.iterdir()):
148+
if candidate.is_file():
149+
value = read_text(candidate)
150+
if value is not None:
151+
vulnerabilities[candidate.name] = value
152+
153+
data: dict[str, Any] = {
154+
"sysfs_root": str(root),
155+
"vendor": cpuinfo.get("vendor_id") or cpuinfo.get("CPU implementer") or platform.machine(),
156+
"arch": platform.machine(),
157+
"model_name": normalize_whitespace(cpuinfo.get("model name") or cpuinfo.get("Processor")),
158+
"family": cpuinfo.get("cpu family"),
159+
"model": cpuinfo.get("model"),
160+
"stepping": cpuinfo.get("stepping"),
161+
"kernel": _detect_kernel_release(root),
162+
"smt_control": read_text(cpu_root / "smt" / "control"),
163+
"intel_pstate": {
164+
"exists": intel_pstate_root.exists(),
165+
"status": read_text(intel_pstate_root / "status"),
166+
"no_turbo": read_text(intel_pstate_root / "no_turbo"),
167+
},
168+
"amd_pstate": {
169+
"exists": amd_pstate_root.exists(),
170+
"status": read_text(amd_pstate_root / "status"),
171+
},
172+
"cpufreq_boost": read_text(cpufreq_root / "boost"),
173+
"policies": policies,
174+
"cpuidle_states": _cpuidle_data(root),
175+
"thermal": thermal,
176+
"hwmon": hwmon,
177+
"topology": _topology_data(root),
178+
"numa": _numa_data(root),
179+
"vulnerabilities": vulnerabilities,
180+
"warnings": ["Fan control is platform-specific; not directly controlled unless safely exposed."],
181+
"vendor_notes": [],
182+
}
183+
184+
vendor = str(data["vendor"])
185+
if "AuthenticAMD" in vendor:
186+
data = annotate_amd(data)
187+
if "ARM" in vendor or "aarch64" in vendor or "arm" in str(data["arch"]).lower():
188+
data = annotate_arm(data)
189+
190+
return data

cpuoptctl/cpuopt_intel.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
from typing import Iterable
4+
5+
6+
PROFILE_EPP_ORDER: dict[str, list[str]] = {
7+
"performance": ["performance"],
8+
"balanced": ["balance_performance", "balance_power", "default", "performance"],
9+
"quiet": ["power", "balance_power", "balance_performance"],
10+
"latency": ["performance"],
11+
"ai-inference": ["performance", "balance_performance"],
12+
}
13+
14+
PROFILE_GOVERNOR_ORDER: dict[str, list[str]] = {
15+
"performance": ["performance", "schedutil", "powersave"],
16+
"balanced": ["schedutil", "powersave", "performance"],
17+
"quiet": ["powersave", "schedutil"],
18+
"latency": ["performance", "schedutil"],
19+
"ai-inference": ["performance", "schedutil"],
20+
}
21+
22+
23+
def choose_preference(available: Iterable[str], preferred: Iterable[str]) -> str | None:
24+
available_list = [item for item in available if item]
25+
for candidate in preferred:
26+
if candidate in available_list:
27+
return candidate
28+
return None
29+
30+
31+
def epp_for_profile(profile_name: str, available: Iterable[str]) -> str | None:
32+
return choose_preference(available, PROFILE_EPP_ORDER.get(profile_name, []))
33+
34+
35+
def governor_for_profile(profile_name: str, available: Iterable[str]) -> str | None:
36+
return choose_preference(available, PROFILE_GOVERNOR_ORDER.get(profile_name, []))
37+
38+
39+
def epb_for_profile(profile_name: str) -> str | None:
40+
mapping = {
41+
"performance": "0",
42+
"balanced": "6",
43+
"quiet": "15",
44+
"latency": "0",
45+
"ai-inference": "0",
46+
}
47+
return mapping.get(profile_name)

0 commit comments

Comments
 (0)