main 6af0a61

cpu.go RawHistoryFolder

1package system
2
3import (
4	"fmt"
5	"os"
6	"strconv"
7	"strings"
8)
9
10// CPUVendor represents CPU manufacturer
11type CPUVendor string
12
13const (
14	CPUVendorIntel   CPUVendor = "Intel"
15	CPUVendorAMD     CPUVendor = "AMD"
16	CPUVendorUnknown CPUVendor = "Unknown"
17)
18
19// AMDPStateMode represents AMD P-State driver mode
20type AMDPStateMode string
21
22const (
23	AMDPStateModeActive  AMDPStateMode = "active"
24	AMDPStateModeGuided  AMDPStateMode = "guided"
25	AMDPStateModePassive AMDPStateMode = "passive"
26	AMDPStateModeNone    AMDPStateMode = ""
27)
28
29// AMDZenGen represents AMD Zen generation
30type AMDZenGen struct {
31	Generation string // "1", "1+", "2", "3", "3+", "4", "5", "unknown"
32	Label      string // "Zen 1", "Zen+", "Zen 2", etc.
33}
34
35// CPUInfo holds detected CPU information
36type CPUInfo struct {
37	Vendor                CPUVendor
38	ModelName             string // Full CPU model name
39	Microcode             string
40	AMDZenGen             *AMDZenGen // AMD Zen generation (nil for non-AMD)
41	AMDPStateModes        []AMDPStateMode
42	RecommendedPStateMode AMDPStateMode // Recommended mode for this CPU
43}
44
45// DetectCPUVendor reads /proc/cpuinfo and returns the CPU vendor
46func DetectCPUVendor() (CPUVendor, error) {
47	data, err := os.ReadFile("/proc/cpuinfo")
48	if err != nil {
49		return CPUVendorUnknown, fmt.Errorf("failed to read /proc/cpuinfo: %w", err)
50	}
51
52	content := string(data)
53	lines := strings.Split(content, "\n")
54
55	for _, line := range lines {
56		switch {
57		case strings.HasPrefix(line, "vendor_id"):
58			parts := strings.Split(line, ":")
59			if len(parts) < 2 {
60				continue
61			}
62
63			vendor := strings.TrimSpace(parts[1])
64			switch vendor {
65			case "GenuineIntel":
66				return CPUVendorIntel, nil
67			case "AuthenticAMD":
68				return CPUVendorAMD, nil
69			default:
70				return CPUVendorUnknown, nil
71			}
72		}
73	}
74
75	return CPUVendorUnknown, nil
76}
77
78// DetectMicrocode returns the appropriate microcode package based on CPU vendor
79func DetectMicrocode(vendor CPUVendor) string {
80	switch vendor {
81	case CPUVendorIntel:
82		return "intel-ucode"
83	case CPUVendorAMD:
84		return "amd-ucode"
85	default:
86		return ""
87	}
88}
89
90// CheckCPPCSupport checks if CPU supports CPPC (required for AMD P-State active mode)
91func CheckCPPCSupport() bool {
92	// Check /proc/cpuinfo for cppc flag
93	data, err := os.ReadFile("/proc/cpuinfo")
94	if err != nil {
95		return false
96	}
97
98	if strings.Contains(string(data), "cppc") {
99		return true
100	}
101
102	// Check if amd_pstate directory exists
103	_, err = os.Stat("/sys/devices/system/cpu/cpu0/cpufreq/amd_pstate")
104	return err == nil
105}
106
107// DetectAMDPStateModes detects available AMD P-State modes
108func DetectAMDPStateModes() []AMDPStateMode {
109	var modes []AMDPStateMode
110
111	// Try to read available modes from sysfs
112	data, err := os.ReadFile("/sys/devices/system/cpu/amd_pstate/status")
113	if err == nil {
114		// Parse available modes from status file
115		content := string(data)
116		if strings.Contains(content, "active") {
117			modes = append(modes, AMDPStateModeActive)
118		}
119		if strings.Contains(content, "guided") {
120			modes = append(modes, AMDPStateModeGuided)
121		}
122		if strings.Contains(content, "passive") {
123			modes = append(modes, AMDPStateModePassive)
124		}
125
126		// If we found modes, return them
127		if len(modes) > 0 {
128			return modes
129		}
130	}
131
132	// Fallback: detect based on CPPC support
133	switch CheckCPPCSupport() {
134	case true:
135		// Modern AMD Ryzen CPUs support all three modes
136		return []AMDPStateMode{
137			AMDPStateModeActive,
138			AMDPStateModeGuided,
139			AMDPStateModePassive,
140		}
141	default:
142		// Older AMD CPUs only support passive mode
143		return []AMDPStateMode{AMDPStateModePassive}
144	}
145}
146
147// GetAMDPStateModeDescription returns human-readable description for P-State mode
148func GetAMDPStateModeDescription(mode AMDPStateMode) string {
149	switch mode {
150	case AMDPStateModeActive:
151		return "active - Best performance (recommended for desktop/gaming)"
152	case AMDPStateModeGuided:
153		return "guided - Balanced performance and efficiency (recommended for laptops)"
154	case AMDPStateModePassive:
155		return "passive - Maximum compatibility (older CPUs)"
156	default:
157		return string(mode)
158	}
159}
160
161// DetectAMDZenGeneration detects AMD Zen generation based on CPU family and model
162// Reference: https://en.wikichip.org/wiki/amd/cpuid
163func DetectAMDZenGeneration() (*AMDZenGen, error) {
164	data, err := os.ReadFile("/proc/cpuinfo")
165	if err != nil {
166		return nil, fmt.Errorf("failed to read /proc/cpuinfo: %w", err)
167	}
168
169	var cpuFamily, cpuModel int
170	lines := strings.Split(string(data), "\n")
171
172	for _, line := range lines {
173		if strings.HasPrefix(line, "cpu family") {
174			parts := strings.Split(line, ":")
175			if len(parts) >= 2 {
176				val := strings.TrimSpace(parts[1])
177				cpuFamily, _ = strconv.Atoi(val)
178			}
179		}
180		if strings.HasPrefix(line, "model") && !strings.HasPrefix(line, "model name") {
181			parts := strings.Split(line, ":")
182			if len(parts) >= 2 {
183				val := strings.TrimSpace(parts[1])
184				cpuModel, _ = strconv.Atoi(val)
185			}
186		}
187
188		// Break early if we have both values
189		if cpuFamily > 0 && cpuModel >= 0 {
190			break
191		}
192	}
193
194	zenGen := &AMDZenGen{
195		Generation: "unknown",
196		Label:      "Unknown",
197	}
198
199	// Detect Zen generation based on CPU family and model
200	switch cpuFamily {
201	case 23: // Family 17h
202		if cpuModel >= 0 && cpuModel <= 15 {
203			zenGen.Generation = "1"
204			zenGen.Label = "Zen 1"
205		} else if cpuModel >= 16 && cpuModel <= 31 {
206			zenGen.Generation = "1+"
207			zenGen.Label = "Zen+"
208		} else if cpuModel >= 48 && cpuModel <= 127 {
209			zenGen.Generation = "2"
210			zenGen.Label = "Zen 2"
211		} else {
212			zenGen.Generation = "2"
213			zenGen.Label = "Zen 2 (assumed)"
214		}
215	case 25: // Family 19h
216		if cpuModel >= 0 && cpuModel <= 15 {
217			zenGen.Generation = "3"
218			zenGen.Label = "Zen 3"
219		} else if cpuModel >= 32 && cpuModel <= 47 {
220			zenGen.Generation = "3"
221			zenGen.Label = "Zen 3"
222		} else if cpuModel >= 80 && cpuModel <= 95 {
223			zenGen.Generation = "3+"
224			zenGen.Label = "Zen 3+"
225		} else if cpuModel >= 96 && cpuModel <= 175 {
226			zenGen.Generation = "4"
227			zenGen.Label = "Zen 4"
228		} else {
229			zenGen.Generation = "3"
230			zenGen.Label = "Zen 3 (assumed)"
231		}
232	case 26: // Family 1Ah
233		zenGen.Generation = "5"
234		zenGen.Label = "Zen 5"
235	}
236
237	return zenGen, nil
238}
239
240// GetRecommendedPStateMode returns the best recommended mode for each Zen generation
241func GetRecommendedPStateMode(zenGen *AMDZenGen) AMDPStateMode {
242	switch zenGen.Generation {
243	case "1", "1+":
244		return AMDPStateModeNone // Use default acpi-cpufreq
245	case "2":
246		return AMDPStateModePassive // Conservative choice for Zen 2
247	case "3", "3+":
248		return AMDPStateModeActive // Best for Zen 3/3+
249	case "4", "5":
250		return AMDPStateModeActive // Best for Zen 4/5
251	default:
252		return AMDPStateModePassive
253	}
254}
255
256// GetAvailablePStateModes returns available P-State modes for a given Zen generation
257func GetAvailablePStateModes(zenGen *AMDZenGen) []AMDPStateMode {
258	hasActive := kernelVersionAtLeast(6, 3)
259	hasGuided := kernelVersionAtLeast(6, 4)
260
261	switch zenGen.Generation {
262	case "1", "1+":
263		// Zen 1 and Zen+: No CPPC support, use acpi-cpufreq
264		return []AMDPStateMode{}
265	case "2":
266		// Zen 2: Basic CPPC support, passive and active modes
267		modes := []AMDPStateMode{}
268		if hasActive {
269			modes = append(modes, AMDPStateModeActive)
270		}
271		modes = append(modes, AMDPStateModePassive)
272		return modes
273	case "3", "3+", "4", "5":
274		// Zen 3+: Full CPPC support for all modes
275		modes := []AMDPStateMode{}
276		if hasActive {
277			modes = append(modes, AMDPStateModeActive)
278		}
279		if hasGuided {
280			modes = append(modes, AMDPStateModeGuided)
281		}
282		modes = append(modes, AMDPStateModePassive)
283		return modes
284	default:
285		return []AMDPStateMode{AMDPStateModePassive}
286	}
287}
288
289func kernelVersionAtLeast(major, minor int) bool {
290	curMajor, curMinor := detectKernelVersion()
291	if curMajor == 0 && curMinor == 0 {
292		return false
293	}
294	if curMajor > major {
295		return true
296	}
297	if curMajor < major {
298		return false
299	}
300	return curMinor >= minor
301}
302
303func detectKernelVersion() (int, int) {
304	data, err := os.ReadFile("/proc/sys/kernel/osrelease")
305	if err != nil {
306		return 0, 0
307	}
308
309	version := strings.TrimSpace(string(data))
310	parts := strings.Split(version, ".")
311	if len(parts) < 2 {
312		return 0, 0
313	}
314
315	major, _ := strconv.Atoi(parts[0])
316	minorPart := strings.Split(parts[1], "-")[0]
317	minor, _ := strconv.Atoi(minorPart)
318
319	return major, minor
320}
321
322// GetPStateModeDescription returns human-readable description for P-State mode
323func GetPStateModeDescription(mode AMDPStateMode) string {
324	switch mode {
325	case AMDPStateModeActive:
326		return "CPPC autonomous with EPP hints (best performance, desktop/gaming)"
327	case AMDPStateModeGuided:
328		return "CPPC guided autonomous (balanced, laptop/hybrid)"
329	case AMDPStateModePassive:
330		return "CPPC non-autonomous (compatibility, manual governor control)"
331	default:
332		return string(mode)
333	}
334}
335
336// GetAMDPStateKernelParams returns kernel parameters needed for AMD P-State mode
337func GetAMDPStateKernelParams(zenGen *AMDZenGen, mode AMDPStateMode) string {
338	if mode == AMDPStateModeNone || mode == "" {
339		return ""
340	}
341
342	// Some Zen 2 CPUs need shared_mem parameter for active mode
343	if zenGen.Generation == "2" && mode == AMDPStateModeActive {
344		return fmt.Sprintf("amd_pstate=%s amd_pstate.shared_mem=1", mode)
345	}
346
347	return fmt.Sprintf("amd_pstate=%s", mode)
348}
349
350// GetCPUModelName extracts the CPU model name from /proc/cpuinfo
351func GetCPUModelName() string {
352	data, err := os.ReadFile("/proc/cpuinfo")
353	if err != nil {
354		return ""
355	}
356
357	lines := strings.Split(string(data), "\n")
358	for _, line := range lines {
359		if strings.HasPrefix(line, "model name") {
360			parts := strings.Split(line, ":")
361			if len(parts) >= 2 {
362				return strings.TrimSpace(parts[1])
363			}
364		}
365	}
366
367	return ""
368}
369
370// DetectCPUInfo detects all CPU information in one call (DRY)
371func DetectCPUInfo() (*CPUInfo, error) {
372	vendor, err := DetectCPUVendor()
373	if err != nil {
374		return nil, err
375	}
376
377	info := &CPUInfo{
378		Vendor:    vendor,
379		ModelName: GetCPUModelName(),
380		Microcode: DetectMicrocode(vendor),
381	}
382
383	// Detect AMD-specific features only for AMD CPUs
384	if vendor == CPUVendorAMD {
385		zenGen, err := DetectAMDZenGeneration()
386		if err == nil {
387			info.AMDZenGen = zenGen
388			info.AMDPStateModes = GetAvailablePStateModes(zenGen)
389			info.RecommendedPStateMode = GetRecommendedPStateMode(zenGen)
390		}
391	}
392
393	return info, nil
394}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close