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
This commit is contained in:
346
internal/cpu/detection.go
Normal file
346
internal/cpu/detection.go
Normal file
@ -0,0 +1,346 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user