Files
dbbackup/internal/cpu/detection.go
Renz 9b3c3f2b1b Initial commit: Database Backup Tool v1.1.0
- PostgreSQL and MySQL support
- Interactive TUI with fixed menu navigation
- Line-by-line progress display
- CPU-aware parallel processing
- Cross-platform build support
- Configuration settings menu
- Silent mode for TUI operations
2025-10-22 19:27:38 +00:00

346 lines
8.5 KiB
Go

package cpu
import (
"fmt"
"runtime"
"strconv"
"strings"
"os"
"os/exec"
"bufio"
)
// CPUInfo holds information about the system CPU
type CPUInfo struct {
LogicalCores int `json:"logical_cores"`
PhysicalCores int `json:"physical_cores"`
Architecture string `json:"architecture"`
ModelName string `json:"model_name"`
MaxFrequency float64 `json:"max_frequency_mhz"`
CacheSize string `json:"cache_size"`
Vendor string `json:"vendor"`
Features []string `json:"features"`
}
// Detector provides CPU detection functionality
type Detector struct {
info *CPUInfo
}
// NewDetector creates a new CPU detector
func NewDetector() *Detector {
return &Detector{}
}
// DetectCPU detects CPU information for the current system
func (d *Detector) DetectCPU() (*CPUInfo, error) {
if d.info != nil {
return d.info, nil
}
info := &CPUInfo{
LogicalCores: runtime.NumCPU(),
Architecture: runtime.GOARCH,
}
// Platform-specific detection
switch runtime.GOOS {
case "linux":
if err := d.detectLinux(info); err != nil {
return info, fmt.Errorf("linux CPU detection failed: %w", err)
}
case "darwin":
if err := d.detectDarwin(info); err != nil {
return info, fmt.Errorf("darwin CPU detection failed: %w", err)
}
case "windows":
if err := d.detectWindows(info); err != nil {
return info, fmt.Errorf("windows CPU detection failed: %w", err)
}
default:
// Fallback for unsupported platforms
info.PhysicalCores = info.LogicalCores
info.ModelName = "Unknown"
info.Vendor = "Unknown"
}
d.info = info
return info, nil
}
// detectLinux detects CPU information on Linux systems
func (d *Detector) detectLinux(info *CPUInfo) error {
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
physicalCoreCount := make(map[string]bool)
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "" {
continue
}
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "model name":
if info.ModelName == "" {
info.ModelName = value
}
case "vendor_id":
if info.Vendor == "" {
info.Vendor = value
}
case "cpu MHz":
if freq, err := strconv.ParseFloat(value, 64); err == nil && info.MaxFrequency < freq {
info.MaxFrequency = freq
}
case "cache size":
if info.CacheSize == "" {
info.CacheSize = value
}
case "flags", "Features":
if len(info.Features) == 0 {
info.Features = strings.Fields(value)
}
case "physical id":
physicalCoreCount[value] = true
}
}
// Calculate physical cores
if len(physicalCoreCount) > 0 {
info.PhysicalCores = len(physicalCoreCount)
} else {
// Fallback: assume hyperthreading if logical > 1
info.PhysicalCores = info.LogicalCores
if info.LogicalCores > 1 {
info.PhysicalCores = info.LogicalCores / 2
}
}
// Try to get more accurate physical core count from lscpu
if cmd := exec.Command("lscpu"); cmd != nil {
if output, err := cmd.Output(); err == nil {
d.parseLscpu(string(output), info)
}
}
return scanner.Err()
}
// parseLscpu parses lscpu output for more accurate CPU information
func (d *Detector) parseLscpu(output string, info *CPUInfo) {
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "Core(s) per socket":
if cores, err := strconv.Atoi(value); err == nil {
if sockets := d.getSocketCount(output); sockets > 0 {
info.PhysicalCores = cores * sockets
}
}
case "Model name":
info.ModelName = value
case "Vendor ID":
info.Vendor = value
case "CPU max MHz":
if freq, err := strconv.ParseFloat(value, 64); err == nil {
info.MaxFrequency = freq
}
}
}
}
// getSocketCount extracts socket count from lscpu output
func (d *Detector) getSocketCount(output string) int {
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "Socket(s):") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
if sockets, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
return sockets
}
}
}
}
return 1 // Default to 1 socket
}
// detectDarwin detects CPU information on macOS systems
func (d *Detector) detectDarwin(info *CPUInfo) error {
// Get CPU brand
if output, err := exec.Command("sysctl", "-n", "machdep.cpu.brand_string").Output(); err == nil {
info.ModelName = strings.TrimSpace(string(output))
}
// Get physical cores
if output, err := exec.Command("sysctl", "-n", "hw.physicalcpu").Output(); err == nil {
if cores, err := strconv.Atoi(strings.TrimSpace(string(output))); err == nil {
info.PhysicalCores = cores
}
}
// Get max frequency
if output, err := exec.Command("sysctl", "-n", "hw.cpufrequency_max").Output(); err == nil {
if freq, err := strconv.ParseFloat(strings.TrimSpace(string(output)), 64); err == nil {
info.MaxFrequency = freq / 1000000 // Convert Hz to MHz
}
}
// Get vendor
if output, err := exec.Command("sysctl", "-n", "machdep.cpu.vendor").Output(); err == nil {
info.Vendor = strings.TrimSpace(string(output))
}
// Get cache size
if output, err := exec.Command("sysctl", "-n", "hw.l3cachesize").Output(); err == nil {
if cache, err := strconv.Atoi(strings.TrimSpace(string(output))); err == nil {
info.CacheSize = fmt.Sprintf("%d KB", cache/1024)
}
}
return nil
}
// detectWindows detects CPU information on Windows systems
func (d *Detector) detectWindows(info *CPUInfo) error {
// Use wmic to get CPU information
cmd := exec.Command("wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed", "/format:list")
output, err := cmd.Output()
if err != nil {
return err
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "Name":
if value != "" {
info.ModelName = value
}
case "NumberOfCores":
if cores, err := strconv.Atoi(value); err == nil {
info.PhysicalCores = cores
}
case "MaxClockSpeed":
if freq, err := strconv.ParseFloat(value, 64); err == nil {
info.MaxFrequency = freq
}
}
}
// Get vendor information
cmd = exec.Command("wmic", "cpu", "get", "Manufacturer", "/format:list")
if output, err := cmd.Output(); err == nil {
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Manufacturer=") {
info.Vendor = strings.TrimSpace(strings.SplitN(line, "=", 2)[1])
break
}
}
}
return nil
}
// CalculateOptimalJobs calculates optimal job count based on CPU info and workload type
func (d *Detector) CalculateOptimalJobs(workloadType string, maxJobs int) (int, error) {
info, err := d.DetectCPU()
if err != nil {
return 1, err
}
var optimal int
switch workloadType {
case "cpu-intensive":
// For CPU-intensive tasks, use physical cores
optimal = info.PhysicalCores
case "io-intensive":
// For I/O intensive tasks, can use more jobs than cores
optimal = info.LogicalCores * 2
case "balanced":
// Balanced workload, use logical cores
optimal = info.LogicalCores
default:
optimal = info.LogicalCores
}
// Apply safety limits
if optimal < 1 {
optimal = 1
}
if maxJobs > 0 && optimal > maxJobs {
optimal = maxJobs
}
return optimal, nil
}
// GetCPUInfo returns the detected CPU information
func (d *Detector) GetCPUInfo() *CPUInfo {
return d.info
}
// FormatCPUInfo returns a formatted string representation of CPU info
func (info *CPUInfo) FormatCPUInfo() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Architecture: %s\n", info.Architecture))
sb.WriteString(fmt.Sprintf("Logical Cores: %d\n", info.LogicalCores))
sb.WriteString(fmt.Sprintf("Physical Cores: %d\n", info.PhysicalCores))
if info.ModelName != "" {
sb.WriteString(fmt.Sprintf("Model: %s\n", info.ModelName))
}
if info.Vendor != "" {
sb.WriteString(fmt.Sprintf("Vendor: %s\n", info.Vendor))
}
if info.MaxFrequency > 0 {
sb.WriteString(fmt.Sprintf("Max Frequency: %.2f MHz\n", info.MaxFrequency))
}
if info.CacheSize != "" {
sb.WriteString(fmt.Sprintf("Cache Size: %s\n", info.CacheSize))
}
return sb.String()
}