- 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
346 lines
8.5 KiB
Go
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()
|
|
} |