mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-16 01:22:00 +00:00
feat: detect CPU generation instead of vendor for instance types (#40)
This commit is contained in:
@@ -8,6 +8,7 @@ require (
|
||||
github.com/gofiber/fiber/v2 v2.52.9
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kdomanski/iso9660 v0.4.0
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/mulgadc/predastore v1.0.0
|
||||
github.com/mulgadc/viperblock v1.0.10
|
||||
github.com/nats-io/nats-server/v2 v2.11.6
|
||||
@@ -69,7 +70,6 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.12.6 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
||||
+232
-140
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
cpuid "github.com/klauspost/cpuid/v2"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
handlers_ec2_account "github.com/mulgadc/hive/hive/handlers/ec2/account"
|
||||
@@ -126,33 +127,181 @@ type Daemon struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// cpuToVendor maps CPU model patterns to CPU vendors.
|
||||
// Patterns are checked in order; more specific patterns should come first.
|
||||
var cpuToVendor = []struct {
|
||||
pattern string
|
||||
vendor cpuVendor
|
||||
}{
|
||||
{"EPYC", vendorAMD},
|
||||
{"Xeon", vendorIntel},
|
||||
{"Graviton", vendorARM},
|
||||
{"Apple", vendorARM},
|
||||
{"ARM", vendorARM},
|
||||
{"AMD", vendorAMD}, // catches Ryzen, Threadripper, etc.
|
||||
// cpuGeneration represents a specific CPU microarchitecture generation
|
||||
// and the AWS instance families it maps to.
|
||||
type cpuGeneration struct {
|
||||
name string // e.g. "Intel Ice Lake", "AMD Genoa"
|
||||
families []string // e.g. ["t3", "c6i", "m6i", "r6i"]
|
||||
}
|
||||
|
||||
// vendorFromCPU returns the CPU vendor based on the CPU model string and architecture.
|
||||
func vendorFromCPU(cpuModel, arch string) cpuVendor {
|
||||
if arch == "arm64" {
|
||||
return vendorARM
|
||||
var (
|
||||
// Intel generations
|
||||
genIntelBroadwell = cpuGeneration{"Intel Broadwell", []string{"t2", "c4", "m4", "r4"}}
|
||||
genIntelSkylake = cpuGeneration{"Intel Skylake/Cascade Lake", []string{"t3", "c5", "m5", "r5"}}
|
||||
genIntelIceLake = cpuGeneration{"Intel Ice Lake", []string{"t3", "c6i", "m6i", "r6i"}}
|
||||
genIntelSapphireRapids = cpuGeneration{"Intel Sapphire Rapids", []string{"t3", "c7i", "m7i", "r7i"}}
|
||||
genIntelGraniteRapids = cpuGeneration{"Intel Granite Rapids", []string{"t3", "c8i", "m8i", "r8i"}}
|
||||
|
||||
// AMD generations
|
||||
genAMDZen = cpuGeneration{"AMD Zen/Zen2 (Naples/Rome)", []string{"t3a", "c5a", "m5a", "r5a"}}
|
||||
genAMDZen3 = cpuGeneration{"AMD Zen 3 (Milan)", []string{"t3a", "c6a", "m6a", "r6a"}}
|
||||
genAMDZen4 = cpuGeneration{"AMD Zen 4 (Genoa)", []string{"t3a", "c7a", "m7a", "r7a"}}
|
||||
genAMDZen5 = cpuGeneration{"AMD Zen 5 (Turin)", []string{"t3a", "c8a", "m8a", "r8a"}}
|
||||
|
||||
// ARM generations
|
||||
genARMNeoverseN1 = cpuGeneration{"ARM Neoverse N1 (Graviton2)", []string{"t4g", "c6g", "m6g", "r6g"}}
|
||||
genARMNeoverseV1 = cpuGeneration{"ARM Neoverse V1 (Graviton3)", []string{"t4g", "c7g", "m7g", "r7g"}}
|
||||
genARMNeoverseV2 = cpuGeneration{"ARM Neoverse V2 (Graviton4)", []string{"t4g", "c8g", "m8g", "r8g"}}
|
||||
|
||||
// Unknown/fallback — expose only burstable family
|
||||
genUnknownIntel = cpuGeneration{"Unknown Intel", []string{"t3"}}
|
||||
genUnknownAMD = cpuGeneration{"Unknown AMD", []string{"t3a"}}
|
||||
genUnknownARM = cpuGeneration{"Unknown ARM", []string{"t4g"}}
|
||||
genUnknown = cpuGeneration{"Unknown", []string{"t3"}}
|
||||
)
|
||||
|
||||
// detectCPUGeneration detects the CPU microarchitecture generation using CPUID.
|
||||
func detectCPUGeneration() cpuGeneration {
|
||||
switch cpuid.CPU.VendorID {
|
||||
case cpuid.Intel:
|
||||
return detectIntelGeneration(cpuid.CPU.Family, cpuid.CPU.Model)
|
||||
case cpuid.AMD:
|
||||
return detectAMDGeneration(cpuid.CPU.Family, cpuid.CPU.Model)
|
||||
default:
|
||||
if runtime.GOARCH == "arm64" {
|
||||
return detectARMGeneration()
|
||||
}
|
||||
slog.Warn("CPUID vendor not recognized, falling back to brand string detection",
|
||||
"vendorID", cpuid.CPU.VendorID, "brand", cpuid.CPU.BrandName)
|
||||
}
|
||||
cpuUpper := strings.ToUpper(cpuModel)
|
||||
for _, entry := range cpuToVendor {
|
||||
if strings.Contains(cpuUpper, strings.ToUpper(entry.pattern)) {
|
||||
return entry.vendor
|
||||
return detectGenerationFromBrand(cpuid.CPU.BrandName, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// detectIntelGeneration maps Intel CPUID Family 6 model numbers to generations.
|
||||
func detectIntelGeneration(family, model int) cpuGeneration {
|
||||
if family != 6 {
|
||||
slog.Warn("Unrecognized Intel CPU family, exposing t3 only", "family", family, "model", model)
|
||||
return genUnknownIntel
|
||||
}
|
||||
|
||||
switch model {
|
||||
case 79, 86: // Broadwell server (BDX, BDX-DE)
|
||||
return genIntelBroadwell
|
||||
case 85: // Skylake-SP / Cascade Lake-SP
|
||||
return genIntelSkylake
|
||||
case 106, 108: // Ice Lake server (ICX, ICX-D)
|
||||
return genIntelIceLake
|
||||
case 143, 207: // Sapphire Rapids (SPR, EMR)
|
||||
return genIntelSapphireRapids
|
||||
case 173, 174: // Granite Rapids (GNR, GNR-D)
|
||||
return genIntelGraniteRapids
|
||||
|
||||
// Consumer/desktop mapped to nearest server generation
|
||||
case 151, 154: // Alder Lake
|
||||
return genIntelIceLake
|
||||
case 183, 191: // Raptor Lake
|
||||
return genIntelSapphireRapids
|
||||
case 197, 198: // Arrow Lake
|
||||
return genIntelGraniteRapids
|
||||
}
|
||||
|
||||
slog.Warn("Unrecognized Intel CPU model, exposing t3 only", "family", family, "model", model, "brand", cpuid.CPU.BrandName)
|
||||
return genUnknownIntel
|
||||
}
|
||||
|
||||
// detectAMDGeneration maps AMD CPUID family/model to generations.
|
||||
func detectAMDGeneration(family, model int) cpuGeneration {
|
||||
switch family {
|
||||
case 23: // Zen, Zen+, Zen 2 (Naples, Rome, Matisse, etc.)
|
||||
return genAMDZen
|
||||
case 25: // Zen 3 and Zen 4 share family 25; model ranges distinguish them
|
||||
// Zen 3 models: 0x00-0x0F (Milan/Vermeer), 0x20-0x5F (Rembrandt/Barcelo)
|
||||
// Zen 4 models: 0x10-0x1F (Genoa), 0x60+ (Raphael/Phoenix)
|
||||
isZen3 := model < 0x10 || (model >= 0x20 && model < 0x60)
|
||||
if isZen3 {
|
||||
return genAMDZen3
|
||||
}
|
||||
return genAMDZen4
|
||||
case 26: // Zen 5 (Turin, Granite Ridge)
|
||||
return genAMDZen5
|
||||
}
|
||||
|
||||
slog.Warn("Unrecognized AMD CPU family, exposing t3a only", "family", family, "model", model, "brand", cpuid.CPU.BrandName)
|
||||
return genUnknownAMD
|
||||
}
|
||||
|
||||
// detectARMGeneration detects ARM CPU generation using brand string and feature flags.
|
||||
func detectARMGeneration() cpuGeneration {
|
||||
brand := strings.ToLower(cpuid.CPU.BrandName)
|
||||
|
||||
// Check for specific Graviton versions
|
||||
if strings.Contains(brand, "graviton4") || strings.Contains(brand, "neoverse-v2") {
|
||||
return genARMNeoverseV2
|
||||
}
|
||||
if strings.Contains(brand, "graviton3") || strings.Contains(brand, "neoverse-v1") {
|
||||
return genARMNeoverseV1
|
||||
}
|
||||
if strings.Contains(brand, "graviton2") || strings.Contains(brand, "neoverse-n1") {
|
||||
return genARMNeoverseN1
|
||||
}
|
||||
|
||||
// SVE indicates Neoverse V1+ but cannot distinguish V1 from V2
|
||||
if cpuid.CPU.Has(cpuid.SVE) {
|
||||
slog.Warn("ARM generation detected via SVE heuristic, defaulting to Neoverse V1", "brand", cpuid.CPU.BrandName)
|
||||
return genARMNeoverseV1
|
||||
}
|
||||
|
||||
// Default ARM to Neoverse N1 (most common)
|
||||
slog.Warn("Could not identify ARM generation, defaulting to Neoverse N1", "brand", cpuid.CPU.BrandName)
|
||||
return genARMNeoverseN1
|
||||
}
|
||||
|
||||
// detectGenerationFromBrand is a fallback for VMs/hypervisors where CPUID may be virtualized.
|
||||
func detectGenerationFromBrand(brand, arch string) cpuGeneration {
|
||||
if arch == "arm64" {
|
||||
return detectARMGeneration()
|
||||
}
|
||||
|
||||
brandLower := strings.ToLower(brand)
|
||||
|
||||
// Intel patterns
|
||||
if strings.Contains(brandLower, "xeon") || strings.Contains(brandLower, "intel") {
|
||||
switch {
|
||||
case strings.Contains(brandLower, "granite"):
|
||||
return genIntelGraniteRapids
|
||||
case strings.Contains(brandLower, "sapphire"):
|
||||
return genIntelSapphireRapids
|
||||
case strings.Contains(brandLower, "ice lake") || strings.Contains(brandLower, "icelake"):
|
||||
return genIntelIceLake
|
||||
case strings.Contains(brandLower, "cascade") || strings.Contains(brandLower, "skylake"):
|
||||
return genIntelSkylake
|
||||
case strings.Contains(brandLower, "broadwell"):
|
||||
return genIntelBroadwell
|
||||
default:
|
||||
// Generic Intel — default to Skylake (most common in VMs)
|
||||
slog.Warn("Intel CPU detected via brand string but generation unknown, defaulting to Skylake", "brand", brand)
|
||||
return genIntelSkylake
|
||||
}
|
||||
}
|
||||
slog.Warn("Unrecognized CPU model, defaulting to Intel vendor", "cpuModel", cpuModel)
|
||||
return vendorIntel
|
||||
|
||||
// AMD patterns
|
||||
if strings.Contains(brandLower, "epyc") || strings.Contains(brandLower, "amd") || strings.Contains(brandLower, "ryzen") {
|
||||
switch {
|
||||
case strings.Contains(brandLower, "turin"):
|
||||
return genAMDZen5
|
||||
case strings.Contains(brandLower, "genoa") || strings.Contains(brandLower, "9004") || strings.Contains(brandLower, "raphael"):
|
||||
return genAMDZen4
|
||||
case strings.Contains(brandLower, "milan") || strings.Contains(brandLower, "7003") || strings.Contains(brandLower, "vermeer"):
|
||||
return genAMDZen3
|
||||
default:
|
||||
// Generic AMD — default to Zen/Zen2
|
||||
slog.Warn("AMD CPU detected via brand string but generation unknown, defaulting to Zen/Zen2", "brand", brand)
|
||||
return genAMDZen
|
||||
}
|
||||
}
|
||||
|
||||
slog.Warn("Unrecognized CPU, exposing t3 only", "brand", brand, "arch", arch)
|
||||
return genUnknown
|
||||
}
|
||||
|
||||
// getSystemMemory returns the total system memory in GB
|
||||
@@ -198,73 +347,14 @@ func getSystemMemory() (float64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getCPUModel returns the CPU model name for the host system
|
||||
func getCPUModel() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// macOS: use sysctl
|
||||
cmd := exec.Command("sysctl", "-n", "machdep.cpu.brand_string")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get CPU model on macOS: %w", err)
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
|
||||
case "linux":
|
||||
// Linux: read from /proc/cpuinfo
|
||||
file, err := os.Open("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open /proc/cpuinfo: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "model name") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
return strings.TrimSpace(parts[1]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("model name not found in /proc/cpuinfo")
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
type instanceSize struct {
|
||||
suffix string
|
||||
vcpus int
|
||||
memoryGB float64
|
||||
}
|
||||
|
||||
type cpuVendor int
|
||||
|
||||
const (
|
||||
vendorIntel cpuVendor = iota
|
||||
vendorAMD
|
||||
vendorARM
|
||||
)
|
||||
|
||||
func (v cpuVendor) String() string {
|
||||
switch v {
|
||||
case vendorIntel:
|
||||
return "Intel"
|
||||
case vendorAMD:
|
||||
return "AMD"
|
||||
case vendorARM:
|
||||
return "ARM"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type instanceFamilyDef struct {
|
||||
name string
|
||||
vendor cpuVendor
|
||||
sizes []instanceSize
|
||||
currentGen bool
|
||||
}
|
||||
@@ -344,61 +434,67 @@ var memorySizesSmall = slices.Clone(memorySizes[:6])
|
||||
// - Legacy (pre-gen4): a1, c1, c3, cc1, cc2, cg1, cr1, hi1, hs1, m1, m2, m3, r3, t1
|
||||
var instanceFamilyDefs = []instanceFamilyDef{
|
||||
// Burstable
|
||||
{name: "t2", vendor: vendorIntel, sizes: burstableSizes, currentGen: false},
|
||||
{name: "t3", vendor: vendorIntel, sizes: burstableSizes, currentGen: true},
|
||||
{name: "t3a", vendor: vendorAMD, sizes: burstableSizes, currentGen: true},
|
||||
{name: "t4g", vendor: vendorARM, sizes: burstableSizes, currentGen: true},
|
||||
{name: "t2", sizes: burstableSizes, currentGen: false},
|
||||
{name: "t3", sizes: burstableSizes, currentGen: true},
|
||||
{name: "t3a", sizes: burstableSizes, currentGen: true},
|
||||
{name: "t4g", sizes: burstableSizes, currentGen: true},
|
||||
|
||||
// General Purpose (1:4 vCPU:memory)
|
||||
{name: "m4", vendor: vendorIntel, sizes: gpSizesSmall, currentGen: false},
|
||||
{name: "m5", vendor: vendorIntel, sizes: gpSizes, currentGen: true},
|
||||
{name: "m5a", vendor: vendorAMD, sizes: gpSizes, currentGen: true},
|
||||
{name: "m6i", vendor: vendorIntel, sizes: gpSizes, currentGen: true},
|
||||
{name: "m6a", vendor: vendorAMD, sizes: gpSizes, currentGen: true},
|
||||
{name: "m6g", vendor: vendorARM, sizes: gpSizesSmall, currentGen: true},
|
||||
{name: "m7i", vendor: vendorIntel, sizes: gpSizes, currentGen: true},
|
||||
{name: "m7a", vendor: vendorAMD, sizes: gpSizes, currentGen: true},
|
||||
{name: "m7g", vendor: vendorARM, sizes: gpSizesSmall, currentGen: true},
|
||||
{name: "m8i", vendor: vendorIntel, sizes: gpSizes, currentGen: true},
|
||||
{name: "m8a", vendor: vendorAMD, sizes: gpSizes, currentGen: true},
|
||||
{name: "m8g", vendor: vendorARM, sizes: gpSizesSmall, currentGen: true},
|
||||
{name: "m4", sizes: gpSizesSmall, currentGen: false},
|
||||
{name: "m5", sizes: gpSizes, currentGen: true},
|
||||
{name: "m5a", sizes: gpSizes, currentGen: true},
|
||||
{name: "m6i", sizes: gpSizes, currentGen: true},
|
||||
{name: "m6a", sizes: gpSizes, currentGen: true},
|
||||
{name: "m6g", sizes: gpSizesSmall, currentGen: true},
|
||||
{name: "m7i", sizes: gpSizes, currentGen: true},
|
||||
{name: "m7a", sizes: gpSizes, currentGen: true},
|
||||
{name: "m7g", sizes: gpSizesSmall, currentGen: true},
|
||||
{name: "m8i", sizes: gpSizes, currentGen: true},
|
||||
{name: "m8a", sizes: gpSizes, currentGen: true},
|
||||
{name: "m8g", sizes: gpSizesSmall, currentGen: true},
|
||||
|
||||
// Compute Optimized (1:2 vCPU:memory)
|
||||
{name: "c4", vendor: vendorIntel, sizes: computeSizesSmall, currentGen: false},
|
||||
{name: "c5", vendor: vendorIntel, sizes: computeSizes, currentGen: true},
|
||||
{name: "c5a", vendor: vendorAMD, sizes: computeSizes, currentGen: true},
|
||||
{name: "c6i", vendor: vendorIntel, sizes: computeSizes, currentGen: true},
|
||||
{name: "c6a", vendor: vendorAMD, sizes: computeSizes, currentGen: true},
|
||||
{name: "c6g", vendor: vendorARM, sizes: computeSizesSmall, currentGen: true},
|
||||
{name: "c7i", vendor: vendorIntel, sizes: computeSizes, currentGen: true},
|
||||
{name: "c7a", vendor: vendorAMD, sizes: computeSizes, currentGen: true},
|
||||
{name: "c7g", vendor: vendorARM, sizes: computeSizesSmall, currentGen: true},
|
||||
{name: "c8i", vendor: vendorIntel, sizes: computeSizes, currentGen: true},
|
||||
{name: "c8a", vendor: vendorAMD, sizes: computeSizes, currentGen: true},
|
||||
{name: "c8g", vendor: vendorARM, sizes: computeSizesSmall, currentGen: true},
|
||||
{name: "c4", sizes: computeSizesSmall, currentGen: false},
|
||||
{name: "c5", sizes: computeSizes, currentGen: true},
|
||||
{name: "c5a", sizes: computeSizes, currentGen: true},
|
||||
{name: "c6i", sizes: computeSizes, currentGen: true},
|
||||
{name: "c6a", sizes: computeSizes, currentGen: true},
|
||||
{name: "c6g", sizes: computeSizesSmall, currentGen: true},
|
||||
{name: "c7i", sizes: computeSizes, currentGen: true},
|
||||
{name: "c7a", sizes: computeSizes, currentGen: true},
|
||||
{name: "c7g", sizes: computeSizesSmall, currentGen: true},
|
||||
{name: "c8i", sizes: computeSizes, currentGen: true},
|
||||
{name: "c8a", sizes: computeSizes, currentGen: true},
|
||||
{name: "c8g", sizes: computeSizesSmall, currentGen: true},
|
||||
|
||||
// Memory Optimized (1:8 vCPU:memory)
|
||||
{name: "r4", vendor: vendorIntel, sizes: memorySizesSmall, currentGen: false},
|
||||
{name: "r5", vendor: vendorIntel, sizes: memorySizes, currentGen: true},
|
||||
{name: "r5a", vendor: vendorAMD, sizes: memorySizes, currentGen: true},
|
||||
{name: "r6i", vendor: vendorIntel, sizes: memorySizes, currentGen: true},
|
||||
{name: "r6a", vendor: vendorAMD, sizes: memorySizes, currentGen: true},
|
||||
{name: "r6g", vendor: vendorARM, sizes: memorySizesSmall, currentGen: true},
|
||||
{name: "r7i", vendor: vendorIntel, sizes: memorySizes, currentGen: true},
|
||||
{name: "r7a", vendor: vendorAMD, sizes: memorySizes, currentGen: true},
|
||||
{name: "r7g", vendor: vendorARM, sizes: memorySizesSmall, currentGen: true},
|
||||
{name: "r8i", vendor: vendorIntel, sizes: memorySizes, currentGen: true},
|
||||
{name: "r8a", vendor: vendorAMD, sizes: memorySizes, currentGen: true},
|
||||
{name: "r8g", vendor: vendorARM, sizes: memorySizesSmall, currentGen: true},
|
||||
{name: "r4", sizes: memorySizesSmall, currentGen: false},
|
||||
{name: "r5", sizes: memorySizes, currentGen: true},
|
||||
{name: "r5a", sizes: memorySizes, currentGen: true},
|
||||
{name: "r6i", sizes: memorySizes, currentGen: true},
|
||||
{name: "r6a", sizes: memorySizes, currentGen: true},
|
||||
{name: "r6g", sizes: memorySizesSmall, currentGen: true},
|
||||
{name: "r7i", sizes: memorySizes, currentGen: true},
|
||||
{name: "r7a", sizes: memorySizes, currentGen: true},
|
||||
{name: "r7g", sizes: memorySizesSmall, currentGen: true},
|
||||
{name: "r8i", sizes: memorySizes, currentGen: true},
|
||||
{name: "r8a", sizes: memorySizes, currentGen: true},
|
||||
{name: "r8g", sizes: memorySizesSmall, currentGen: true},
|
||||
}
|
||||
|
||||
// generateInstanceTypes creates the instance type map for the given CPU vendor.
|
||||
// It generates all instance families matching the vendor across burstable,
|
||||
// general purpose, compute optimized, and memory optimized categories.
|
||||
func generateInstanceTypes(v cpuVendor, arch string) map[string]*ec2.InstanceTypeInfo {
|
||||
// generateInstanceTypes creates the instance type map for the given CPU generation.
|
||||
// It generates all instance families matching the generation's family list across
|
||||
// burstable, general purpose, compute optimized, and memory optimized categories.
|
||||
func generateInstanceTypes(gen cpuGeneration, arch string) map[string]*ec2.InstanceTypeInfo {
|
||||
// Build a set of allowed families for fast lookup
|
||||
allowed := make(map[string]bool, len(gen.families))
|
||||
for _, f := range gen.families {
|
||||
allowed[f] = true
|
||||
}
|
||||
|
||||
instanceTypes := make(map[string]*ec2.InstanceTypeInfo)
|
||||
for _, def := range instanceFamilyDefs {
|
||||
if def.vendor != v {
|
||||
if !allowed[def.name] {
|
||||
continue
|
||||
}
|
||||
burstable := strings.HasPrefix(def.name, "t")
|
||||
@@ -438,31 +534,25 @@ func NewResourceManager() *ResourceManager {
|
||||
totalMemGB = 8.0 // Default to 8GB if we can't get the actual memory
|
||||
}
|
||||
|
||||
// Get CPU model for instance family detection
|
||||
cpuModel, err := getCPUModel()
|
||||
if err != nil {
|
||||
slog.Warn("Failed to get CPU model, using default", "err", err)
|
||||
cpuModel = "Unknown"
|
||||
}
|
||||
|
||||
// Determine architecture
|
||||
arch := "x86_64"
|
||||
if runtime.GOARCH == "arm64" {
|
||||
arch = "arm64"
|
||||
}
|
||||
|
||||
// Detect CPU vendor and generate instance types
|
||||
vendor := vendorFromCPU(cpuModel, arch)
|
||||
instanceTypes := generateInstanceTypes(vendor, arch)
|
||||
// Detect CPU generation and generate matching instance types
|
||||
gen := detectCPUGeneration()
|
||||
instanceTypes := generateInstanceTypes(gen, arch)
|
||||
|
||||
if len(instanceTypes) == 0 {
|
||||
slog.Error("No instance types generated, daemon will be unable to run VMs",
|
||||
"vendor", vendor, "arch", arch, "cpuModel", cpuModel)
|
||||
"generation", gen.name, "arch", arch)
|
||||
}
|
||||
|
||||
slog.Info("System resources detected",
|
||||
"vCPUs", numCPU, "memGB", totalMemGB, "cpu", cpuModel,
|
||||
"vendor", vendor, "instanceTypes", len(instanceTypes), "os", runtime.GOOS)
|
||||
"vCPUs", numCPU, "memGB", totalMemGB,
|
||||
"generation", gen.name, "families", gen.families,
|
||||
"instanceTypes", len(instanceTypes), "os", runtime.GOOS)
|
||||
|
||||
return &ResourceManager{
|
||||
availableVCPU: numCPU,
|
||||
@@ -1981,9 +2071,11 @@ func (d *Daemon) StartInstance(instance *vm.VM) error {
|
||||
return // Startup failed, LaunchInstance handles the error
|
||||
}
|
||||
|
||||
// Runtime crash — handle it (clean exits are expected from QMP shutdown)
|
||||
// Handle exit: crash vs clean shutdown
|
||||
if waitErr != nil {
|
||||
d.handleInstanceCrash(instance, waitErr)
|
||||
} else {
|
||||
slog.Info("VM process exited cleanly", "instance", instance.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
+261
-110
@@ -450,9 +450,9 @@ func TestGetInstanceTypeInfos(t *testing.T) {
|
||||
infos := rm.GetInstanceTypeInfos()
|
||||
|
||||
require.NotEmpty(t, infos, "Should return at least one instance type")
|
||||
// With comprehensive families, expect 49+ types (ARM) up to 128 (Intel)
|
||||
assert.True(t, len(infos) >= 49,
|
||||
"Should have at least 49 instance types, got %d", len(infos))
|
||||
// With generation-specific families, minimum is 7 (unknown/burstable-only) up to 31 (current-gen)
|
||||
assert.True(t, len(infos) >= 7,
|
||||
"Should have at least 7 instance types, got %d", len(infos))
|
||||
|
||||
// Verify structure of returned instance type info
|
||||
for _, info := range infos {
|
||||
@@ -470,35 +470,12 @@ func TestGetInstanceTypeInfos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCPUDetection tests CPU model detection
|
||||
// TestCPUDetection tests that detectCPUGeneration returns a valid generation on the current host
|
||||
func TestCPUDetection(t *testing.T) {
|
||||
cpuModel, err := getCPUModel()
|
||||
require.NoError(t, err, "Should be able to detect CPU model")
|
||||
assert.NotEmpty(t, cpuModel, "CPU model should not be empty")
|
||||
|
||||
t.Logf("Detected CPU model: %s", cpuModel)
|
||||
|
||||
// Test vendor mapping with correct architectures
|
||||
testCases := []struct {
|
||||
cpuModel string
|
||||
arch string
|
||||
expected cpuVendor
|
||||
}{
|
||||
{"AMD EPYC 7551", "x86_64", vendorAMD},
|
||||
{"AMD Ryzen 9 7950X", "x86_64", vendorAMD},
|
||||
{"Intel Xeon E5-2686", "x86_64", vendorIntel},
|
||||
{"Apple M1", "arm64", vendorARM},
|
||||
{"AWS Graviton2", "arm64", vendorARM},
|
||||
{"Unknown CPU", "x86_64", vendorIntel},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.cpuModel, func(t *testing.T) {
|
||||
v := vendorFromCPU(tt.cpuModel, tt.arch)
|
||||
assert.Equal(t, tt.expected, v, "Wrong vendor for %s on %s", tt.cpuModel, tt.arch)
|
||||
t.Logf("CPU: %s (%s) -> Vendor: %s", tt.cpuModel, tt.arch, v)
|
||||
})
|
||||
}
|
||||
gen := detectCPUGeneration()
|
||||
assert.NotEmpty(t, gen.name, "Generation name should not be empty")
|
||||
assert.NotEmpty(t, gen.families, "Generation families should not be empty")
|
||||
t.Logf("Detected CPU generation: %s, families: %v", gen.name, gen.families)
|
||||
}
|
||||
|
||||
// TestGetAvailableInstanceTypeInfos_ResourceFiltering tests that instance types are filtered by available resources
|
||||
@@ -3232,60 +3209,138 @@ func countFamily(types map[string]*ec2.InstanceTypeInfo, prefix string) int {
|
||||
return count
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_Intel(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
// Intel: t2(7)+t3(7) + m4(6)+m5(8)+m6i(8)+m7i(8)+m8i(8) + c4(6)+c5(8)+c6i(8)+c7i(8)+c8i(8) + r4(6)+r5(8)+r6i(8)+r7i(8)+r8i(8) = 128
|
||||
assert.Len(t, types, 128)
|
||||
// --- Generation-specific instance type tests ---
|
||||
|
||||
for _, prefix := range []string{"t2.", "t3.", "m7i.", "m8i.", "c5.", "c7i.", "r5.", "r7i."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Intel types to include %s family", prefix)
|
||||
func TestGenerateInstanceTypes_IntelIceLake(t *testing.T) {
|
||||
types := generateInstanceTypes(genIntelIceLake, "x86_64")
|
||||
// t3(7) + c6i(8) + m6i(8) + r6i(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t3.", "c6i.", "m6i.", "r6i."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Ice Lake types to include %s family", prefix)
|
||||
}
|
||||
|
||||
// Verify AMD/ARM families are NOT present
|
||||
// Verify other generation families are NOT present
|
||||
for name := range types {
|
||||
assert.False(t, strings.HasPrefix(name, "t3a."), "Intel should not have t3a: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "t4g."), "Intel should not have t4g: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "m7a."), "Intel should not have m7a: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "m7g."), "Intel should not have m7g: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "t3a."), "Ice Lake should not have t3a: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "c5."), "Ice Lake should not have c5: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "c7i."), "Ice Lake should not have c7i: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_AMD(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorAMD, "x86_64")
|
||||
// AMD: t3a(7) + m5a(8)+m6a(8)+m7a(8)+m8a(8) + c5a(8)+c6a(8)+c7a(8)+c8a(8) + r5a(8)+r6a(8)+r7a(8)+r8a(8) = 103
|
||||
assert.Len(t, types, 103)
|
||||
func TestGenerateInstanceTypes_IntelBroadwell(t *testing.T) {
|
||||
types := generateInstanceTypes(genIntelBroadwell, "x86_64")
|
||||
// t2(7) + c4(6) + m4(6) + r4(6) = 25
|
||||
assert.Len(t, types, 25)
|
||||
|
||||
for _, prefix := range []string{"t3a.", "m5a.", "m8a.", "c5a.", "c8a.", "r5a.", "r8a."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected AMD types to include %s family", prefix)
|
||||
}
|
||||
|
||||
// Verify Intel/ARM families are NOT present
|
||||
for name := range types {
|
||||
assert.False(t, strings.HasPrefix(name, "t3."), "AMD should not have t3: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "t4g."), "AMD should not have t4g: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "m7i."), "AMD should not have m7i: %s", name)
|
||||
for _, prefix := range []string{"t2.", "c4.", "m4.", "r4."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Broadwell types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_ARM(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorARM, "arm64")
|
||||
// ARM: t4g(7) + m6g(6)+m7g(6)+m8g(6) + c6g(6)+c7g(6)+c8g(6) + r6g(6)+r7g(6)+r8g(6) = 61
|
||||
assert.Len(t, types, 61)
|
||||
func TestGenerateInstanceTypes_IntelSkylake(t *testing.T) {
|
||||
types := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
// t3(7) + c5(8) + m5(8) + r5(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t4g.", "m7g.", "m8g.", "c7g.", "c8g.", "r7g.", "r8g."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected ARM types to include %s family", prefix)
|
||||
for _, prefix := range []string{"t3.", "c5.", "m5.", "r5."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Skylake types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_IntelSapphireRapids(t *testing.T) {
|
||||
types := generateInstanceTypes(genIntelSapphireRapids, "x86_64")
|
||||
// t3(7) + c7i(8) + m7i(8) + r7i(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t3.", "c7i.", "m7i.", "r7i."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Sapphire Rapids types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_IntelGraniteRapids(t *testing.T) {
|
||||
types := generateInstanceTypes(genIntelGraniteRapids, "x86_64")
|
||||
// t3(7) + c8i(8) + m8i(8) + r8i(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t3.", "c8i.", "m8i.", "r8i."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Granite Rapids types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_AMDZen(t *testing.T) {
|
||||
types := generateInstanceTypes(genAMDZen, "x86_64")
|
||||
// t3a(7) + c5a(8) + m5a(8) + r5a(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t3a.", "c5a.", "m5a.", "r5a."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Zen types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_AMDZen4(t *testing.T) {
|
||||
types := generateInstanceTypes(genAMDZen4, "x86_64")
|
||||
// t3a(7) + c7a(8) + m7a(8) + r7a(8) = 31
|
||||
assert.Len(t, types, 31)
|
||||
|
||||
for _, prefix := range []string{"t3a.", "c7a.", "m7a.", "r7a."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected Zen 4 types to include %s family", prefix)
|
||||
}
|
||||
|
||||
// Verify older AMD families are NOT present
|
||||
for name := range types {
|
||||
assert.False(t, strings.HasPrefix(name, "c5a."), "Zen4 should not have c5a: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "c6a."), "Zen4 should not have c6a: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_ARMN1(t *testing.T) {
|
||||
types := generateInstanceTypes(genARMNeoverseN1, "arm64")
|
||||
// t4g(7) + c6g(6) + m6g(6) + r6g(6) = 25
|
||||
assert.Len(t, types, 25)
|
||||
|
||||
for _, prefix := range []string{"t4g.", "c6g.", "m6g.", "r6g."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected N1 types to include %s family", prefix)
|
||||
}
|
||||
|
||||
// Verify Intel/AMD families are NOT present
|
||||
for name := range types {
|
||||
assert.False(t, strings.HasPrefix(name, "t3."), "ARM should not have t3: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "t3a."), "ARM should not have t3a: %s", name)
|
||||
assert.False(t, strings.HasPrefix(name, "m7i."), "ARM should not have m7i: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_ARMV2(t *testing.T) {
|
||||
types := generateInstanceTypes(genARMNeoverseV2, "arm64")
|
||||
// t4g(7) + c8g(6) + m8g(6) + r8g(6) = 25
|
||||
assert.Len(t, types, 25)
|
||||
|
||||
for _, prefix := range []string{"t4g.", "c8g.", "m8g.", "r8g."} {
|
||||
assert.True(t, hasFamily(types, prefix), "expected V2 types to include %s family", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_UnknownFallback(t *testing.T) {
|
||||
types := generateInstanceTypes(genUnknownIntel, "x86_64")
|
||||
// Unknown Intel: t3 only = 7 types
|
||||
assert.Len(t, types, 7)
|
||||
assert.True(t, hasFamily(types, "t3."), "unknown Intel should have t3")
|
||||
|
||||
types = generateInstanceTypes(genUnknownAMD, "x86_64")
|
||||
assert.Len(t, types, 7)
|
||||
assert.True(t, hasFamily(types, "t3a."), "unknown AMD should have t3a")
|
||||
|
||||
types = generateInstanceTypes(genUnknownARM, "arm64")
|
||||
assert.Len(t, types, 7)
|
||||
assert.True(t, hasFamily(types, "t4g."), "unknown ARM should have t4g")
|
||||
|
||||
types = generateInstanceTypes(genUnknown, "x86_64")
|
||||
assert.Len(t, types, 7)
|
||||
assert.True(t, hasFamily(types, "t3."), "completely unknown should have t3")
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_VerifyBurstableSizes(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
types := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
|
||||
expected := map[string]struct {
|
||||
vcpus int64
|
||||
@@ -3309,51 +3364,61 @@ func TestGenerateInstanceTypes_VerifyBurstableSizes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_ComputeRatio(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
|
||||
// Compute optimized: 1:2 vCPU:memory ratio
|
||||
expected := map[string]struct {
|
||||
// Skylake for c5
|
||||
skylakeTypes := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
expectedSkylake := map[string]struct {
|
||||
vcpus int64
|
||||
memMB int64
|
||||
}{
|
||||
"c5.large": {2, 4096},
|
||||
"c5.xlarge": {4, 8192},
|
||||
"c5.2xlarge": {8, 16384},
|
||||
"c7i.4xlarge": {16, 32768},
|
||||
"c5.large": {2, 4096},
|
||||
"c5.xlarge": {4, 8192},
|
||||
"c5.2xlarge": {8, 16384},
|
||||
}
|
||||
|
||||
for name, exp := range expected {
|
||||
it, ok := types[name]
|
||||
for name, exp := range expectedSkylake {
|
||||
it, ok := skylakeTypes[name]
|
||||
require.True(t, ok, "missing instance type %s", name)
|
||||
assert.Equal(t, exp.vcpus, *it.VCpuInfo.DefaultVCpus, "%s vCPUs", name)
|
||||
assert.Equal(t, exp.memMB, *it.MemoryInfo.SizeInMiB, "%s memory", name)
|
||||
}
|
||||
|
||||
// Sapphire Rapids for c7i
|
||||
sapphireTypes := generateInstanceTypes(genIntelSapphireRapids, "x86_64")
|
||||
it, ok := sapphireTypes["c7i.4xlarge"]
|
||||
require.True(t, ok, "missing instance type c7i.4xlarge")
|
||||
assert.Equal(t, int64(16), *it.VCpuInfo.DefaultVCpus, "c7i.4xlarge vCPUs")
|
||||
assert.Equal(t, int64(32768), *it.MemoryInfo.SizeInMiB, "c7i.4xlarge memory")
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_MemoryRatio(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
|
||||
// Memory optimized: 1:8 vCPU:memory ratio
|
||||
expected := map[string]struct {
|
||||
// Skylake for r5
|
||||
skylakeTypes := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
expectedSkylake := map[string]struct {
|
||||
vcpus int64
|
||||
memMB int64
|
||||
}{
|
||||
"r5.large": {2, 16384},
|
||||
"r5.xlarge": {4, 32768},
|
||||
"r5.2xlarge": {8, 65536},
|
||||
"r7i.4xlarge": {16, 131072},
|
||||
"r5.large": {2, 16384},
|
||||
"r5.xlarge": {4, 32768},
|
||||
"r5.2xlarge": {8, 65536},
|
||||
}
|
||||
|
||||
for name, exp := range expected {
|
||||
it, ok := types[name]
|
||||
for name, exp := range expectedSkylake {
|
||||
it, ok := skylakeTypes[name]
|
||||
require.True(t, ok, "missing instance type %s", name)
|
||||
assert.Equal(t, exp.vcpus, *it.VCpuInfo.DefaultVCpus, "%s vCPUs", name)
|
||||
assert.Equal(t, exp.memMB, *it.MemoryInfo.SizeInMiB, "%s memory", name)
|
||||
}
|
||||
|
||||
// Sapphire Rapids for r7i
|
||||
sapphireTypes := generateInstanceTypes(genIntelSapphireRapids, "x86_64")
|
||||
it, ok := sapphireTypes["r7i.4xlarge"]
|
||||
require.True(t, ok, "missing instance type r7i.4xlarge")
|
||||
assert.Equal(t, int64(16), *it.VCpuInfo.DefaultVCpus, "r7i.4xlarge vCPUs")
|
||||
assert.Equal(t, int64(131072), *it.MemoryInfo.SizeInMiB, "r7i.4xlarge memory")
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_NoSmallSizesForNonBurstable(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
types := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
|
||||
// Non-burstable families should not have nano/micro/small/medium sizes
|
||||
for name := range types {
|
||||
@@ -3368,21 +3433,21 @@ func TestGenerateInstanceTypes_NoSmallSizesForNonBurstable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_OlderFamiliesHaveSmallerSizeRange(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
// Broadwell has m4 = 6 sizes
|
||||
broadwellTypes := generateInstanceTypes(genIntelBroadwell, "x86_64")
|
||||
assert.Equal(t, 6, countFamily(broadwellTypes, "m4."), "m4 should have 6 sizes (large → 16xlarge)")
|
||||
|
||||
// m4 (older Intel GP) should have 6 sizes (large → 16xlarge, no 24xlarge)
|
||||
assert.Equal(t, 6, countFamily(types, "m4."), "m4 should have 6 sizes (large → 16xlarge)")
|
||||
// m5 (newer Intel GP) should have 8 sizes (large → 24xlarge)
|
||||
assert.Equal(t, 8, countFamily(types, "m5."), "m5 should have 8 sizes (large → 24xlarge)")
|
||||
// Skylake has m5 = 8 sizes
|
||||
skylakeTypes := generateInstanceTypes(genIntelSkylake, "x86_64")
|
||||
assert.Equal(t, 8, countFamily(skylakeTypes, "m5."), "m5 should have 8 sizes (large → 24xlarge)")
|
||||
}
|
||||
|
||||
func TestGenerateInstanceTypes_BurstableFlag(t *testing.T) {
|
||||
types := generateInstanceTypes(vendorIntel, "x86_64")
|
||||
|
||||
// Previous-gen families: t2, m4, c4, r4
|
||||
// Test Broadwell (has prev-gen families)
|
||||
broadwellTypes := generateInstanceTypes(genIntelBroadwell, "x86_64")
|
||||
prevGen := map[string]bool{"t2": true, "m4": true, "c4": true, "r4": true}
|
||||
|
||||
for name, info := range types {
|
||||
for name, info := range broadwellTypes {
|
||||
isBurstable := strings.HasPrefix(name, "t")
|
||||
family := strings.SplitN(name, ".", 2)[0]
|
||||
assert.Equal(t, isBurstable, *info.BurstablePerformanceSupported,
|
||||
@@ -3390,30 +3455,116 @@ func TestGenerateInstanceTypes_BurstableFlag(t *testing.T) {
|
||||
assert.Equal(t, !prevGen[family], *info.CurrentGeneration,
|
||||
"%s current generation flag mismatch", name)
|
||||
}
|
||||
|
||||
// Test current-gen (Sapphire Rapids) — all families should be currentGen=true
|
||||
sapphireTypes := generateInstanceTypes(genIntelSapphireRapids, "x86_64")
|
||||
for name, info := range sapphireTypes {
|
||||
isBurstable := strings.HasPrefix(name, "t")
|
||||
assert.Equal(t, isBurstable, *info.BurstablePerformanceSupported,
|
||||
"%s burstable flag mismatch", name)
|
||||
assert.True(t, *info.CurrentGeneration,
|
||||
"%s should be current generation", name)
|
||||
}
|
||||
}
|
||||
|
||||
// --- vendorFromCPU ---
|
||||
// --- CPU generation detection tests ---
|
||||
|
||||
func TestVendorFromCPU_ExactMappings(t *testing.T) {
|
||||
assert.Equal(t, vendorAMD, vendorFromCPU("AMD EPYC 7551", "x86_64"))
|
||||
assert.Equal(t, vendorAMD, vendorFromCPU("AMD Ryzen 9 7950X", "x86_64"))
|
||||
assert.Equal(t, vendorAMD, vendorFromCPU("AMD Ryzen Threadripper PRO 5995WX", "x86_64"))
|
||||
assert.Equal(t, vendorIntel, vendorFromCPU("Intel Xeon E5-2686", "x86_64"))
|
||||
assert.Equal(t, vendorARM, vendorFromCPU("Apple M1", "arm64"))
|
||||
assert.Equal(t, vendorARM, vendorFromCPU("AWS Graviton2", "arm64"))
|
||||
assert.Equal(t, vendorARM, vendorFromCPU("ARM Cortex-A72", "arm64"))
|
||||
func TestDetectIntelGeneration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
family int
|
||||
model int
|
||||
expected cpuGeneration
|
||||
}{
|
||||
{"Broadwell BDX", 6, 79, genIntelBroadwell},
|
||||
{"Broadwell BDX-DE", 6, 86, genIntelBroadwell},
|
||||
{"Skylake-SP", 6, 85, genIntelSkylake},
|
||||
{"Ice Lake ICX", 6, 106, genIntelIceLake},
|
||||
{"Ice Lake ICX-D", 6, 108, genIntelIceLake},
|
||||
{"Sapphire Rapids SPR", 6, 143, genIntelSapphireRapids},
|
||||
{"Emerald Rapids EMR", 6, 207, genIntelSapphireRapids},
|
||||
{"Granite Rapids GNR", 6, 173, genIntelGraniteRapids},
|
||||
{"Granite Rapids GNR-D", 6, 174, genIntelGraniteRapids},
|
||||
// Consumer mappings
|
||||
{"Alder Lake", 6, 151, genIntelIceLake},
|
||||
{"Alder Lake P", 6, 154, genIntelIceLake},
|
||||
{"Raptor Lake", 6, 183, genIntelSapphireRapids},
|
||||
{"Raptor Lake P", 6, 191, genIntelSapphireRapids},
|
||||
{"Arrow Lake", 6, 197, genIntelGraniteRapids},
|
||||
{"Arrow Lake S", 6, 198, genIntelGraniteRapids},
|
||||
// Unknown
|
||||
{"Unknown family", 15, 0, genUnknownIntel},
|
||||
{"Unknown model", 6, 255, genUnknownIntel},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gen := detectIntelGeneration(tt.family, tt.model)
|
||||
assert.Equal(t, tt.expected.name, gen.name)
|
||||
assert.Equal(t, tt.expected.families, gen.families)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVendorFromCPU_CaseInsensitive(t *testing.T) {
|
||||
assert.Equal(t, vendorAMD, vendorFromCPU("amd epyc 9654", "x86_64"))
|
||||
func TestDetectAMDGeneration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
family int
|
||||
model int
|
||||
expected cpuGeneration
|
||||
}{
|
||||
{"Naples/Rome family 23", 23, 1, genAMDZen},
|
||||
{"Zen3 Milan model 0x01", 25, 0x01, genAMDZen3},
|
||||
{"Zen3 Vermeer model 0x21", 25, 0x21, genAMDZen3},
|
||||
{"Zen4 Genoa model 0x11", 25, 0x11, genAMDZen4},
|
||||
{"Zen4 Raphael model 0x61", 25, 0x61, genAMDZen4},
|
||||
// Boundary tests for family 25 Zen3/Zen4 split
|
||||
{"Zen3 boundary 0x0F", 25, 0x0F, genAMDZen3},
|
||||
{"Zen4 boundary 0x10", 25, 0x10, genAMDZen4},
|
||||
{"Zen4 boundary 0x1F", 25, 0x1F, genAMDZen4},
|
||||
{"Zen3 boundary 0x20", 25, 0x20, genAMDZen3},
|
||||
{"Zen3 boundary 0x5F", 25, 0x5F, genAMDZen3},
|
||||
{"Zen4 boundary 0x60", 25, 0x60, genAMDZen4},
|
||||
{"Zen4 max model", 25, 0xFF, genAMDZen4},
|
||||
{"Zen5 Turin family 26", 26, 0, genAMDZen5},
|
||||
{"Unknown AMD family", 20, 0, genUnknownAMD},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gen := detectAMDGeneration(tt.family, tt.model)
|
||||
assert.Equal(t, tt.expected.name, gen.name)
|
||||
assert.Equal(t, tt.expected.families, gen.families)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVendorFromCPU_DefaultFallback(t *testing.T) {
|
||||
// Unknown CPU on x86_64 defaults to Intel
|
||||
assert.Equal(t, vendorIntel, vendorFromCPU("Unknown CPU Model XYZ", "x86_64"))
|
||||
// Any CPU on arm64 always returns ARM
|
||||
assert.Equal(t, vendorARM, vendorFromCPU("Unknown CPU Model XYZ", "arm64"))
|
||||
assert.Equal(t, vendorARM, vendorFromCPU("Intel Xeon E5-2686", "arm64"))
|
||||
func TestDetectGenerationFromBrand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
brand string
|
||||
arch string
|
||||
expected cpuGeneration
|
||||
}{
|
||||
{"Intel Skylake brand", "Intel(R) Xeon(R) Platinum 8175M (Skylake)", "x86_64", genIntelSkylake},
|
||||
{"Intel Ice Lake brand", "Intel(R) Xeon(R) Platinum 8375C (Ice Lake)", "x86_64", genIntelIceLake},
|
||||
{"Intel Sapphire brand", "Intel(R) Xeon(R) w9-3495X (Sapphire Rapids)", "x86_64", genIntelSapphireRapids},
|
||||
{"Intel Granite brand", "Intel(R) Xeon(R) 6980P (Granite Rapids)", "x86_64", genIntelGraniteRapids},
|
||||
{"Intel Broadwell brand", "Intel(R) Xeon(R) E5-2686 v4 (Broadwell)", "x86_64", genIntelBroadwell},
|
||||
{"Generic Intel Xeon", "Intel(R) Xeon(R) CPU E5-2686 v4", "x86_64", genIntelSkylake}, // defaults to Skylake
|
||||
{"AMD Milan brand", "AMD EPYC 7003 Milan", "x86_64", genAMDZen3},
|
||||
{"AMD Genoa brand", "AMD EPYC 9004 Series", "x86_64", genAMDZen4},
|
||||
{"Generic AMD EPYC", "AMD EPYC 7551", "x86_64", genAMDZen},
|
||||
{"Completely unknown", "Some Random CPU", "x86_64", genUnknown},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gen := detectGenerationFromBrand(tt.brand, tt.arch)
|
||||
assert.Equal(t, tt.expected.name, gen.name)
|
||||
assert.Equal(t, tt.expected.families, gen.families)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- instanceTypeVCPUs / instanceTypeMemoryMiB nil safety ---
|
||||
|
||||
@@ -102,7 +102,6 @@ type IOThread struct {
|
||||
|
||||
type Config struct {
|
||||
Name string `json:"name"`
|
||||
Daemonize bool `json:"daemonize"`
|
||||
PIDFile string `json:"pid_file"`
|
||||
QMPSocket string `json:"qmp_socket"`
|
||||
EnableKVM bool `json:"enable_kvm"`
|
||||
@@ -128,10 +127,6 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
|
||||
args := []string{}
|
||||
|
||||
if cfg.Daemonize {
|
||||
args = append(args, "-daemonize")
|
||||
}
|
||||
|
||||
if cfg.PIDFile != "" {
|
||||
args = append(args, "-pidfile", cfg.PIDFile)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user