This commit is contained in:
2026-01-11 23:40:18 +01:00
parent 29a99c55e7
commit 6452144fc0
5 changed files with 32278 additions and 16300 deletions
BIN
View File
Binary file not shown.
+66 -66
View File
File diff suppressed because one or more lines are too long
+14091 -16194
View File
File diff suppressed because one or more lines are too long
Executable
+18018
View File
File diff suppressed because one or more lines are too long
+103 -40
View File
@@ -8,6 +8,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"math"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@@ -375,6 +376,11 @@ type DirectDiskMetrics struct {
ReadMBs float64 ReadMBs float64
} }
type CacheSummary struct {
Sizes map[int]int
Counts map[int]int
}
type NetResult struct { type NetResult struct {
Upload float64 `json:"upload_mbps"` Upload float64 `json:"upload_mbps"`
Download float64 `json:"download_mbps"` Download float64 `json:"download_mbps"`
@@ -657,22 +663,32 @@ func collectCPUInfo(ctx context.Context) CPUDetails {
info.CacheL3KB = l3 info.CacheL3KB = l3
} }
} }
if info.CacheL1KB == 0 && info.CacheL2KB == 0 && info.CacheL3KB == 0 { cacheSummary := collectCacheSizes(ctx, info.Cores)
info.CacheL1KB, info.CacheL2KB, info.CacheL3KB = collectCacheSizes(ctx, info.Cores) if info.CacheL1KB == 0 && cacheSummary.Sizes[1] > 0 {
info.CacheL1KB = cacheSummary.Sizes[1]
} }
applyCpuidCacheInfo(&info) if info.CacheL2KB == 0 && cacheSummary.Sizes[2] > 0 {
info.CacheL2KB = cacheSummary.Sizes[2]
}
if info.CacheL3KB == 0 && cacheSummary.Sizes[3] > 0 {
info.CacheL3KB = cacheSummary.Sizes[3]
}
applyCpuidCacheInfo(&info, cacheSummary)
info.Microarchitecture = detectMicroarchitecture(first.ModelName, first.Family) info.Microarchitecture = detectMicroarchitecture(first.ModelName, first.Family)
info.BaseFreqGHz, info.MaxFreqGHz = determineCPUFreqs(ctx, first) info.BaseFreqGHz, info.MaxFreqGHz = determineCPUFreqs(ctx, first)
return info return info
} }
func collectCacheSizes(ctx context.Context, physicalCores int) (l1, l2, l3 int) { func collectCacheSizes(ctx context.Context, physicalCores int) CacheSummary {
cacheDir := "/sys/devices/system/cpu/cpu0/cache" cacheDir := "/sys/devices/system/cpu/cpu0/cache"
entries, err := os.ReadDir(cacheDir) entries, err := os.ReadDir(cacheDir)
if err != nil { summary := CacheSummary{
return 0, 0, 0 Sizes: map[int]int{},
Counts: map[int]int{},
}
if err != nil {
return summary
} }
perLevel := map[int]int{}
for _, entry := range entries { for _, entry := range entries {
if !strings.HasPrefix(entry.Name(), "index") { if !strings.HasPrefix(entry.Name(), "index") {
continue continue
@@ -685,16 +701,14 @@ func collectCacheSizes(ctx context.Context, physicalCores int) (l1, l2, l3 int)
sizePath := filepath.Join(cacheDir, entry.Name(), "size") sizePath := filepath.Join(cacheDir, entry.Name(), "size")
sizeKB := parseCacheSize(readStringFromFile(sizePath)) sizeKB := parseCacheSize(readStringFromFile(sizePath))
if sizeKB > 0 { if sizeKB > 0 {
perLevel[level] += sizeKB summary.Sizes[level] += sizeKB
summary.Counts[level]++
} }
} }
l1 = perLevel[1] return summary
l2 = perLevel[2]
l3 = perLevel[3]
return l1, l2, l3
} }
func applyCpuidCacheInfo(info *CPUDetails) { func applyCpuidCacheInfo(info *CPUDetails, summary CacheSummary) {
cache := cpuid.CPU.Cache cache := cpuid.CPU.Cache
if cache.L1I < 0 && cache.L1D < 0 && cache.L2 < 0 && cache.L3 < 0 { if cache.L1I < 0 && cache.L1D < 0 && cache.L2 < 0 && cache.L3 < 0 {
return return
@@ -706,6 +720,9 @@ func applyCpuidCacheInfo(info *CPUDetails) {
cores = 1 cores = 1
} }
} }
if summary.Counts == nil {
summary.Counts = map[int]int{}
}
l1Bytes := 0 l1Bytes := 0
if cache.L1I > 0 { if cache.L1I > 0 {
l1Bytes += cache.L1I l1Bytes += cache.L1I
@@ -717,10 +734,18 @@ func applyCpuidCacheInfo(info *CPUDetails) {
info.CacheL1KB = int(int64(l1Bytes) * int64(cores) / 1024) info.CacheL1KB = int(int64(l1Bytes) * int64(cores) / 1024)
} }
if cache.L2 > 0 && info.CacheL2KB == 0 { if cache.L2 > 0 && info.CacheL2KB == 0 {
info.CacheL2KB = int(int64(cache.L2) * int64(cores) / 1024) l2Slices := summary.Counts[2]
if l2Slices <= 0 {
l2Slices = cores
}
info.CacheL2KB = maxInt(info.CacheL2KB, int((int64(cache.L2)*int64(l2Slices))/1024))
} }
if cache.L3 > 0 && info.CacheL3KB == 0 { if cache.L3 > 0 && info.CacheL3KB == 0 {
info.CacheL3KB = int(cache.L3 / 1024) l3Slices := summary.Counts[3]
if l3Slices <= 0 {
l3Slices = 1
}
info.CacheL3KB = maxInt(info.CacheL3KB, int((int64(cache.L3)*int64(l3Slices))/1024))
} }
} }
@@ -797,7 +822,7 @@ func determineCPUFreqs(ctx context.Context, info cpu.InfoStat) (float64, float64
max = maxSpeed / 1000 max = maxSpeed / 1000
} }
} }
return base, max return round2(base), round2(max)
} }
func parseDMIDecodeSpeed(output string, re *regexp.Regexp) float64 { func parseDMIDecodeSpeed(output string, re *regexp.Regexp) float64 {
@@ -888,6 +913,16 @@ func collectRAMInfo(ctx context.Context) RAMDetails {
} }
slotsTotal, slotsUsed, maxCapacity, ecc, layout := collectSMBIOSMemory() slotsTotal, slotsUsed, maxCapacity, ecc, layout := collectSMBIOSMemory()
if len(layout) > 0 {
correctSlots := len(layout)
if slotsTotal == 0 || slotsTotal < correctSlots || slotsTotal > correctSlots*4 {
debugf("Correction RAM slots_total=%d => %d (layout=%d)", slotsTotal, correctSlots, len(layout))
slotsTotal = correctSlots
}
if slotsUsed == 0 {
slotsUsed = len(layout)
}
}
if slotsTotal > 0 { if slotsTotal > 0 {
res.SlotsTotal = slotsTotal res.SlotsTotal = slotsTotal
} }
@@ -901,14 +936,18 @@ func collectRAMInfo(ctx context.Context) RAMDetails {
res.Layout = layout res.Layout = layout
if res.MaxCapacityMB == 0 { if res.MaxCapacityMB == 0 {
debugf("RAM fallback max capacity: SMBIOS=0 => utiliser total=%dMB", res.TotalMB)
res.MaxCapacityMB = res.TotalMB res.MaxCapacityMB = res.TotalMB
} }
if res.SlotsTotal == 0 && len(layout) > 0 { if res.SlotsTotal == 0 && len(layout) > 0 {
debugf("RAM fallback slots total: SMBIOS=0 => %d modules détectés", len(layout))
res.SlotsTotal = len(layout) res.SlotsTotal = len(layout)
} }
if res.SlotsUsed == 0 && len(layout) > 0 { if res.SlotsUsed == 0 && len(layout) > 0 {
debugf("RAM fallback slots used: SMBIOS=0 => %d modules détectés", len(layout))
res.SlotsUsed = len(layout) res.SlotsUsed = len(layout)
} }
debugf("RAM result: max=%dMB slotsTotal=%d slotsUsed=%d layout=%d modules", res.MaxCapacityMB, res.SlotsTotal, res.SlotsUsed, len(layout))
return res return res
} }
@@ -964,19 +1003,20 @@ func parseSMBIOSPhysicalMemoryArray(info *smbios.Info) (slotsTotal int, ecc bool
if table.Type != smbiosTableTypePhysicalMemoryArray { if table.Type != smbiosTableTypePhysicalMemoryArray {
continue continue
} }
if val, err := table.GetByteAt(11); err == nil && val > 0 { if val, err := table.GetByteAt(13); err == nil && val > 0 {
debugf("[SMBIOS RAW] Handle=%s slots_byte=0x%X (%d)", table.Handle, val, val)
slotsTotal = int(val) slotsTotal = int(val)
} }
if val, err := table.GetByteAt(6); err == nil { if val, err := table.GetByteAt(6); err == nil {
ecc = val != 0 && val != 3 ecc = val != 0 && val != 3
} }
if val, err := table.GetWordAt(7); err == nil { if dw, err := table.GetDWordAt(7); err == nil {
if val >= 0x8000 && table.Len() >= 0x10 { if dw == 0x80000000 && table.Len() >= 0x10 {
if ext, err := table.GetDWordAt(12); err == nil && ext > 0 { if ext, err := table.GetDWordAt(12); err == nil && ext > 0 {
maxCapacityMB = int(ext / (1024 * 1024)) maxCapacityMB = int(ext / (1024 * 1024))
} }
} else if val > 0 { } else if dw > 0 {
maxCapacityMB = int(val) maxCapacityMB = int(dw / 1024)
} }
} }
break break
@@ -2140,12 +2180,12 @@ func runCPUBench(ctx context.Context) CPUResult {
singleEvents, singleDuration := runSysbenchCPU(ctx, "1") singleEvents, singleDuration := runSysbenchCPU(ctx, "1")
multiThreads := resolveThreadCount(ctx) multiThreads := resolveThreadCount(ctx)
multiEvents, multiDuration := runSysbenchCPU(ctx, multiThreads) multiEvents, multiDuration := runSysbenchCPU(ctx, multiThreads)
res.EventsPerSecSingle = singleEvents res.EventsPerSecSingle = round2(singleEvents)
res.EventsPerSecMulti = multiEvents res.EventsPerSecMulti = round2(multiEvents)
res.EventsPerSec = averagePositive(singleEvents, multiEvents) res.EventsPerSec = round2(averagePositive(singleEvents, multiEvents))
res.DurationSec = singleDuration + multiDuration res.DurationSec = round2(singleDuration + multiDuration)
res.ScoreSingle = singleEvents res.ScoreSingle = res.EventsPerSecSingle
res.ScoreMulti = multiEvents res.ScoreMulti = res.EventsPerSecMulti
res.Score = res.EventsPerSec res.Score = res.EventsPerSec
return res return res
} }
@@ -2211,13 +2251,13 @@ func runMemBench(ctx context.Context) MemResult {
if out, err := safeRun(ctx, "sysbench", args...); err == nil { if out, err := safeRun(ctx, "sysbench", args...); err == nil {
if matches := regexp.MustCompile(`([\d\.]+)\s+MiB/sec`).FindStringSubmatch(out); len(matches) > 1 { if matches := regexp.MustCompile(`([\d\.]+)\s+MiB/sec`).FindStringSubmatch(out); len(matches) > 1 {
if val, err := strconv.ParseFloat(matches[1], 64); err == nil { if val, err := strconv.ParseFloat(matches[1], 64); err == nil {
res.Throughput = val res.Throughput = round2(val)
res.Score = val res.Score = res.Throughput
} }
} else if matches := regexp.MustCompile(`transferred:\s+([\d\.]+)\s+MiB`).FindStringSubmatch(out); len(matches) > 1 { } else if matches := regexp.MustCompile(`transferred:\s+([\d\.]+)\s+MiB`).FindStringSubmatch(out); len(matches) > 1 {
if val, err := strconv.ParseFloat(matches[1], 64); err == nil { if val, err := strconv.ParseFloat(matches[1], 64); err == nil {
res.Throughput = val res.Throughput = round2(val)
res.Score = val res.Score = res.Throughput
} }
} }
} }
@@ -2266,7 +2306,12 @@ func runDiskBench(ctx context.Context) DiskResult {
res.IOPSWrite = convertFloat(write["iops"]) res.IOPSWrite = convertFloat(write["iops"])
} }
} }
res.Score = averagePositive(res.ReadMBs, res.WriteMBs) res.Score = round2(averagePositive(res.ReadMBs, res.WriteMBs))
res.ReadMBs = round2(res.ReadMBs)
res.WriteMBs = round2(res.WriteMBs)
res.LatencyMs = round2(res.LatencyMs)
res.IOPSRead = round2(res.IOPSRead)
res.IOPSWrite = round2(res.IOPSWrite)
debugf("[3/6] Bench disque final -> read=%.2f MB/s write=%.2f MB/s score=%.2f", res.ReadMBs, res.WriteMBs, res.Score) debugf("[3/6] Bench disque final -> read=%.2f MB/s write=%.2f MB/s score=%.2f", res.ReadMBs, res.WriteMBs, res.Score)
return res return res
} }
@@ -2283,6 +2328,13 @@ func convertBW(value interface{}) float64 {
return 0 return 0
} }
func maxInt(a, b int) int {
if b > a {
return b
}
return a
}
func runDirectDiskTests(ctx context.Context, benchDir string, mountInfo mountEntry) DirectDiskMetrics { func runDirectDiskTests(ctx context.Context, benchDir string, mountInfo mountEntry) DirectDiskMetrics {
if benchDir == "" { if benchDir == "" {
return DirectDiskMetrics{} return DirectDiskMetrics{}
@@ -2334,8 +2386,8 @@ func runDirectDiskTests(ctx context.Context, benchDir string, mountInfo mountEnt
cleanupPaths(cleanup) cleanupPaths(cleanup)
metrics := DirectDiskMetrics{ metrics := DirectDiskMetrics{
WriteMBs: safeThroughput(totalWriteMB, totalWriteDur), WriteMBs: round2(safeThroughput(totalWriteMB, totalWriteDur)),
ReadMBs: safeThroughput(totalReadMB, totalReadDur), ReadMBs: round2(safeThroughput(totalReadMB, totalReadDur)),
} }
debugf("[3/6] Bench disque direct résumé -> write=%.2f MB/s read=%.2f MB/s", metrics.WriteMBs, metrics.ReadMBs) debugf("[3/6] Bench disque direct résumé -> write=%.2f MB/s read=%.2f MB/s", metrics.WriteMBs, metrics.ReadMBs)
return metrics return metrics
@@ -2430,6 +2482,13 @@ func fillBuffer(buf []byte) {
buf[i] = byte(i % 256) buf[i] = byte(i % 256)
} }
} }
func round2(v float64) float64 {
if math.IsNaN(v) || math.IsInf(v, 0) {
return v
}
return math.Round(v*100) / 100
}
func convertFloat(value interface{}) float64 { func convertFloat(value interface{}) float64 {
if v, ok := value.(float64); ok { if v, ok := value.(float64); ok {
return v return v
@@ -2472,23 +2531,27 @@ func runNetBench(ctx context.Context) NetResult {
} }
if end, ok := iperfJSON["end"].(map[string]interface{}); ok { if end, ok := iperfJSON["end"].(map[string]interface{}); ok {
if sumSent, ok := end["sum_sent"].(map[string]interface{}); ok { if sumSent, ok := end["sum_sent"].(map[string]interface{}); ok {
res.Upload = convertFloat(sumSent["bits_per_second"]) / 1000 / 1000 if val := convertFloat(sumSent["bits_per_second"]); val > 0 {
res.Upload = round2(val / 1000 / 1000)
}
if val := convertFloat(sumSent["jitter_ms"]); val > 0 { if val := convertFloat(sumSent["jitter_ms"]); val > 0 {
res.JitterMs = floatPtr(val) res.JitterMs = floatPtr(round2(val))
} }
if val := convertFloat(sumSent["lost_percent"]); val > 0 { if val := convertFloat(sumSent["lost_percent"]); val > 0 {
res.PacketLossPct = floatPtr(val) res.PacketLossPct = floatPtr(round2(val))
} }
} }
if sumRecv, ok := end["sum_received"].(map[string]interface{}); ok { if sumRecv, ok := end["sum_received"].(map[string]interface{}); ok {
res.Download = convertFloat(sumRecv["bits_per_second"]) / 1000 / 1000 if val := convertFloat(sumRecv["bits_per_second"]); val > 0 {
res.Download = round2(val / 1000 / 1000)
}
} }
} }
res.Score = averagePositive(res.Upload, res.Download) res.Score = round2(averagePositive(res.Upload, res.Download))
// Ping test // Ping test
if cfg.Benchmarks.Network.Server != "" { if cfg.Benchmarks.Network.Server != "" {
if ping := measurePing(ctx, cfg.Benchmarks.Network.Server); ping > 0 { if ping := measurePing(ctx, cfg.Benchmarks.Network.Server); ping > 0 {
res.PingMs = ping res.PingMs = round2(ping)
} }
} }
return res return res