3
This commit is contained in:
BIN
bench-client
BIN
bench-client
Binary file not shown.
File diff suppressed because one or more lines are too long
18018
debug.txt
Executable file
18018
debug.txt
Executable file
File diff suppressed because one or more lines are too long
143
main.go
143
main.go
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user