577 lines
17 KiB
Go
577 lines
17 KiB
Go
package platform
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestResolveBenchmarkProfile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
profile string
|
|
want benchmarkProfileSpec
|
|
}{
|
|
{
|
|
name: "default",
|
|
profile: "",
|
|
want: benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStandard, BaselineSec: 15, WarmupSec: 45, SteadySec: 480, NCCLSec: 180, CooldownSec: 0},
|
|
},
|
|
{
|
|
name: "stability",
|
|
profile: "stability",
|
|
want: benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStability, BaselineSec: 30, WarmupSec: 120, SteadySec: 3600, NCCLSec: 300, CooldownSec: 0},
|
|
},
|
|
{
|
|
name: "overnight",
|
|
profile: "overnight",
|
|
want: benchmarkProfileSpec{Name: NvidiaBenchmarkProfileOvernight, BaselineSec: 60, WarmupSec: 180, SteadySec: 27000, NCCLSec: 600, CooldownSec: 0},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := resolveBenchmarkProfile(tc.profile)
|
|
if got != tc.want {
|
|
t.Fatalf("profile=%q got %+v want %+v", tc.profile, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildBenchmarkSteadyPlanStandard(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
labels, phases, basePhaseSec, mixedPhaseSec := buildBenchmarkSteadyPlan(
|
|
benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStandard, SteadySec: 480},
|
|
benchmarkPrecisionPhases,
|
|
func(label string) string { return label },
|
|
)
|
|
if len(labels) != 5 || len(phases) != 5 {
|
|
t.Fatalf("labels=%d phases=%d want 5", len(labels), len(phases))
|
|
}
|
|
if basePhaseSec != 60 {
|
|
t.Fatalf("basePhaseSec=%d want 60", basePhaseSec)
|
|
}
|
|
if mixedPhaseSec != 300 {
|
|
t.Fatalf("mixedPhaseSec=%d want 300", mixedPhaseSec)
|
|
}
|
|
if phases[len(phases)-1].PlanLabel != "mixed" || phases[len(phases)-1].DurationSec != 300 {
|
|
t.Fatalf("mixed phase=%+v want duration 300", phases[len(phases)-1])
|
|
}
|
|
if benchmarkPlanDurationsCSV(phases) != "60,60,60,60,300" {
|
|
t.Fatalf("durations=%q", benchmarkPlanDurationsCSV(phases))
|
|
}
|
|
}
|
|
|
|
func TestBuildBenchmarkSteadyPlanStability(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, phases, basePhaseSec, mixedPhaseSec := buildBenchmarkSteadyPlan(
|
|
benchmarkProfileSpec{Name: NvidiaBenchmarkProfileStability, SteadySec: 3600},
|
|
benchmarkPrecisionPhases,
|
|
func(label string) string { return label },
|
|
)
|
|
if basePhaseSec != 300 {
|
|
t.Fatalf("basePhaseSec=%d want 300", basePhaseSec)
|
|
}
|
|
if mixedPhaseSec != 3600 {
|
|
t.Fatalf("mixedPhaseSec=%d want 3600", mixedPhaseSec)
|
|
}
|
|
if benchmarkPlanDurationsCSV(phases) != "300,300,300,300,3600" {
|
|
t.Fatalf("durations=%q", benchmarkPlanDurationsCSV(phases))
|
|
}
|
|
}
|
|
|
|
func TestBuildBenchmarkSteadyPlanOvernight(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, phases, basePhaseSec, mixedPhaseSec := buildBenchmarkSteadyPlan(
|
|
benchmarkProfileSpec{Name: NvidiaBenchmarkProfileOvernight, SteadySec: 27000},
|
|
benchmarkPrecisionPhases,
|
|
func(label string) string { return label },
|
|
)
|
|
if basePhaseSec != 3600 {
|
|
t.Fatalf("basePhaseSec=%d want 3600", basePhaseSec)
|
|
}
|
|
if mixedPhaseSec != 14400 {
|
|
t.Fatalf("mixedPhaseSec=%d want 14400", mixedPhaseSec)
|
|
}
|
|
if benchmarkPlanDurationsCSV(phases) != "3600,3600,3600,3600,14400" {
|
|
t.Fatalf("durations=%q", benchmarkPlanDurationsCSV(phases))
|
|
}
|
|
}
|
|
|
|
func TestSplitBenchmarkRowsByPlannedPhaseUsesPhaseDurations(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
phases := []benchmarkPlannedPhase{
|
|
{PlanLabel: "fp8", MetricStage: "fp8", DurationSec: 10},
|
|
{PlanLabel: "fp16", MetricStage: "fp16", DurationSec: 10},
|
|
{PlanLabel: "mixed", MetricStage: "mixed", DurationSec: 50},
|
|
}
|
|
rows := []GPUMetricRow{
|
|
{ElapsedSec: 5},
|
|
{ElapsedSec: 15},
|
|
{ElapsedSec: 25},
|
|
{ElapsedSec: 65},
|
|
}
|
|
got := splitBenchmarkRowsByPlannedPhase(rows, phases)
|
|
if len(got["fp8"]) != 1 {
|
|
t.Fatalf("fp8 rows=%d want 1", len(got["fp8"]))
|
|
}
|
|
if len(got["fp16"]) != 1 {
|
|
t.Fatalf("fp16 rows=%d want 1", len(got["fp16"]))
|
|
}
|
|
if len(got["mixed"]) != 2 {
|
|
t.Fatalf("mixed rows=%d want 2", len(got["mixed"]))
|
|
}
|
|
}
|
|
|
|
func TestBenchmarkSupportedPrecisionsSkipsFP4BeforeBlackwell(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := benchmarkSupportedPrecisions("9.0"); strings.Join(got, ",") != "int8,fp8,fp16,fp32" {
|
|
t.Fatalf("supported=%v", got)
|
|
}
|
|
if got := benchmarkSupportedPrecisions("10.0"); strings.Join(got, ",") != "int8,fp8,fp16,fp32" {
|
|
t.Fatalf("supported=%v", got)
|
|
}
|
|
}
|
|
|
|
func TestBenchmarkPlannedPhaseStatus(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
raw string
|
|
wantStatus string
|
|
}{
|
|
{name: "ok", raw: "status=OK\n", wantStatus: "OK"},
|
|
{name: "failed", raw: "phase_error=fp16\n", wantStatus: "FAILED"},
|
|
{name: "unsupported", raw: "cublasLt_profiles=unsupported\nphase_error=fp4\n", wantStatus: "UNSUPPORTED"},
|
|
}
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, _ := benchmarkPlannedPhaseStatus([]byte(tc.raw))
|
|
if got != tc.wantStatus {
|
|
t.Fatalf("status=%q want %q", got, tc.wantStatus)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBenchmarkCalibrationThrottleReasonIgnoresPowerReasons(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
before := BenchmarkThrottleCounters{}
|
|
if got := benchmarkCalibrationThrottleReason(before, BenchmarkThrottleCounters{SWPowerCapUS: 1_000_000}); got != "" {
|
|
t.Fatalf("sw_power_cap should be ignored, got %q", got)
|
|
}
|
|
if got := benchmarkCalibrationThrottleReason(before, BenchmarkThrottleCounters{HWPowerBrakeSlowdownUS: 1_000_000}); got != "" {
|
|
t.Fatalf("hw_power_brake should be ignored, got %q", got)
|
|
}
|
|
if got := benchmarkCalibrationThrottleReason(before, BenchmarkThrottleCounters{HWThermalSlowdownUS: 1_000_000}); got != "hw_thermal" {
|
|
t.Fatalf("hw_thermal mismatch: got %q", got)
|
|
}
|
|
if got := benchmarkCalibrationThrottleReason(before, BenchmarkThrottleCounters{SWThermalSlowdownUS: 1_000_000}); got != "sw_thermal" {
|
|
t.Fatalf("sw_thermal mismatch: got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestResetBenchmarkGPUsSkipsWithoutRoot(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
oldGeteuid := benchmarkGeteuid
|
|
oldExec := satExecCommand
|
|
benchmarkGeteuid = func() int { return 1000 }
|
|
satExecCommand = func(name string, args ...string) *exec.Cmd {
|
|
t.Fatalf("unexpected command: %s %v", name, args)
|
|
return nil
|
|
}
|
|
t.Cleanup(func() {
|
|
benchmarkGeteuid = oldGeteuid
|
|
satExecCommand = oldExec
|
|
})
|
|
|
|
var logs []string
|
|
failed := resetBenchmarkGPUs(context.Background(), filepath.Join(t.TempDir(), "verbose.log"), []int{0, 2}, func(line string) {
|
|
logs = append(logs, line)
|
|
})
|
|
if got, want := strings.Join(logs, "\n"), "power benchmark pre-flight: root privileges unavailable, GPU reset skipped"; !strings.Contains(got, want) {
|
|
t.Fatalf("logs=%q want substring %q", got, want)
|
|
}
|
|
if len(failed) != 2 || failed[0] != 0 || failed[1] != 2 {
|
|
t.Fatalf("failed=%v want [0 2]", failed)
|
|
}
|
|
}
|
|
|
|
func TestResetBenchmarkGPUsResetsEachGPU(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
script := filepath.Join(dir, "nvidia-smi")
|
|
argsLog := filepath.Join(dir, "args.log")
|
|
if err := os.WriteFile(script, []byte("#!/bin/sh\nprintf '%s\\n' \"$*\" >> "+argsLog+"\nprintf 'ok\\n'\n"), 0755); err != nil {
|
|
t.Fatalf("write script: %v", err)
|
|
}
|
|
|
|
oldGeteuid := benchmarkGeteuid
|
|
oldSleep := benchmarkSleep
|
|
oldLookPath := satLookPath
|
|
benchmarkGeteuid = func() int { return 0 }
|
|
benchmarkSleep = func(time.Duration) {}
|
|
satLookPath = func(file string) (string, error) {
|
|
if file == "nvidia-smi" {
|
|
return script, nil
|
|
}
|
|
return exec.LookPath(file)
|
|
}
|
|
t.Cleanup(func() {
|
|
benchmarkGeteuid = oldGeteuid
|
|
benchmarkSleep = oldSleep
|
|
satLookPath = oldLookPath
|
|
})
|
|
|
|
failed := resetBenchmarkGPUs(context.Background(), filepath.Join(dir, "verbose.log"), []int{2, 5}, nil)
|
|
if len(failed) != 0 {
|
|
t.Fatalf("failed=%v want no failures", failed)
|
|
}
|
|
raw, err := os.ReadFile(argsLog)
|
|
if err != nil {
|
|
t.Fatalf("read args log: %v", err)
|
|
}
|
|
got := strings.Fields(string(raw))
|
|
want := []string{"-i", "2", "-r", "-i", "5", "-r"}
|
|
if strings.Join(got, " ") != strings.Join(want, " ") {
|
|
t.Fatalf("args=%v want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeNvidiaBenchmarkOptionsPreservesRunNCCLChoice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
opts := normalizeNvidiaBenchmarkOptionsForBenchmark(NvidiaBenchmarkOptions{
|
|
Profile: "stability",
|
|
RunNCCL: false,
|
|
})
|
|
if opts.Profile != NvidiaBenchmarkProfileStability {
|
|
t.Fatalf("profile=%q want %q", opts.Profile, NvidiaBenchmarkProfileStability)
|
|
}
|
|
if opts.RunNCCL {
|
|
t.Fatalf("RunNCCL should stay false when explicitly disabled")
|
|
}
|
|
}
|
|
|
|
func TestInitialBenchmarkCalibrationLimitW(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
info benchmarkGPUInfo
|
|
want int
|
|
}{
|
|
{
|
|
name: "prefers default tdp over current derated limit",
|
|
info: benchmarkGPUInfo{
|
|
PowerLimitW: 500,
|
|
DefaultPowerLimitW: 600,
|
|
MaxPowerLimitW: 600,
|
|
},
|
|
want: 600,
|
|
},
|
|
{
|
|
name: "caps default tdp to reported max limit",
|
|
info: benchmarkGPUInfo{
|
|
PowerLimitW: 500,
|
|
DefaultPowerLimitW: 700,
|
|
MaxPowerLimitW: 650,
|
|
},
|
|
want: 650,
|
|
},
|
|
{
|
|
name: "falls back to current limit when default missing",
|
|
info: benchmarkGPUInfo{
|
|
PowerLimitW: 525,
|
|
MaxPowerLimitW: 600,
|
|
},
|
|
want: 525,
|
|
},
|
|
{
|
|
name: "falls back to max limit when only that is known",
|
|
info: benchmarkGPUInfo{
|
|
MaxPowerLimitW: 575,
|
|
},
|
|
want: 575,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := initialBenchmarkCalibrationLimitW(tc.info); got != tc.want {
|
|
t.Fatalf("initialBenchmarkCalibrationLimitW(%+v)=%d want %d", tc.info, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseBenchmarkBurnLog(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
raw := strings.Join([]string{
|
|
"loader=bee-gpu-burn",
|
|
"[gpu 0] device=NVIDIA H100",
|
|
"[gpu 0] compute_capability=9.0",
|
|
"[gpu 0] backend=cublasLt",
|
|
"[gpu 0] duration_s=10",
|
|
"[gpu 0] int8_tensor[0]=READY dim=16384x16384x8192 block=128 stream=0",
|
|
"[gpu 0] fp16_tensor[0]=READY dim=4096x4096x4096 block=128 stream=0",
|
|
"[gpu 0] fp8_e4m3[0]=READY dim=8192x8192x4096 block=128 stream=0",
|
|
"[gpu 0] int8_tensor_iterations=80",
|
|
"[gpu 0] fp16_tensor_iterations=200",
|
|
"[gpu 0] fp8_e4m3_iterations=50",
|
|
"[gpu 0] status=OK",
|
|
}, "\n")
|
|
|
|
got := parseBenchmarkBurnLog(raw)
|
|
if got.Backend != "cublasLt" {
|
|
t.Fatalf("backend=%q want cublasLt", got.Backend)
|
|
}
|
|
if got.ComputeCapability != "9.0" {
|
|
t.Fatalf("compute capability=%q want 9.0", got.ComputeCapability)
|
|
}
|
|
if len(got.Profiles) != 3 {
|
|
t.Fatalf("profiles=%d want 3", len(got.Profiles))
|
|
}
|
|
if got.Profiles[0].TeraOpsPerSec <= 0 {
|
|
t.Fatalf("profile[0] teraops=%f want >0", got.Profiles[0].TeraOpsPerSec)
|
|
}
|
|
if got.Profiles[0].Category != "fp16_bf16" {
|
|
t.Fatalf("profile[0] category=%q want fp16_bf16", got.Profiles[0].Category)
|
|
}
|
|
if got.Profiles[1].Category != "fp8" {
|
|
t.Fatalf("profile[1] category=%q want fp8", got.Profiles[1].Category)
|
|
}
|
|
if got.Profiles[2].Category != "int8" {
|
|
t.Fatalf("profile[2] category=%q want int8", got.Profiles[2].Category)
|
|
}
|
|
if got.Profiles[2].Weight != 0.25 {
|
|
t.Fatalf("profile[2] weight=%f want 0.25", got.Profiles[2].Weight)
|
|
}
|
|
}
|
|
|
|
func TestRenderBenchmarkReportIncludesFindingsAndScores(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := NvidiaBenchmarkResult{
|
|
BenchmarkVersion: benchmarkVersion,
|
|
BenchmarkProfile: NvidiaBenchmarkProfileStandard,
|
|
OverallStatus: "PARTIAL",
|
|
SelectedGPUIndices: []int{0},
|
|
Normalization: BenchmarkNormalization{
|
|
Status: "partial",
|
|
},
|
|
Findings: []string{"GPU 0 spent measurable time under SW power cap."},
|
|
GPUs: []BenchmarkGPUResult{
|
|
{
|
|
Index: 0,
|
|
Name: "NVIDIA H100",
|
|
Status: "OK",
|
|
Steady: BenchmarkTelemetrySummary{
|
|
AvgPowerW: 680,
|
|
AvgTempC: 79,
|
|
AvgGraphicsClockMHz: 1725,
|
|
P95PowerW: 700,
|
|
P95TempC: 82,
|
|
P95GraphicsClockMHz: 1800,
|
|
},
|
|
Scores: BenchmarkScorecard{
|
|
ComputeScore: 1200,
|
|
PowerSustainScore: 96,
|
|
ThermalSustainScore: 88,
|
|
StabilityScore: 92,
|
|
CompositeScore: 1176,
|
|
},
|
|
PrecisionResults: []BenchmarkPrecisionResult{
|
|
{Name: "fp16_tensor", Supported: true, TeraOpsPerSec: 700},
|
|
},
|
|
Throttle: BenchmarkThrottleCounters{
|
|
SWPowerCapUS: 1000000,
|
|
},
|
|
DegradationReasons: []string{"power_capped"},
|
|
},
|
|
},
|
|
Cooling: &BenchmarkCoolingSummary{
|
|
Available: true,
|
|
AvgFanRPM: 9200,
|
|
FanDutyCycleAvailable: true,
|
|
AvgFanDutyCyclePct: 47.5,
|
|
P95FanDutyCyclePct: 62.0,
|
|
},
|
|
}
|
|
|
|
report := renderBenchmarkReport(result)
|
|
for _, needle := range []string{
|
|
"Executive Summary",
|
|
"GPU 0 spent measurable time under SW power cap.",
|
|
"1176.00",
|
|
"fp16_tensor",
|
|
"700.00",
|
|
"Cooling",
|
|
"Average fan duty cycle",
|
|
"47.5%",
|
|
} {
|
|
if !strings.Contains(report, needle) {
|
|
t.Fatalf("report missing %q\n%s", needle, report)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderBenchmarkReportListsUnifiedArtifacts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
report := renderBenchmarkReport(NvidiaBenchmarkResult{
|
|
BenchmarkProfile: NvidiaBenchmarkProfileStandard,
|
|
OverallStatus: "OK",
|
|
SelectedGPUIndices: []int{0},
|
|
Normalization: BenchmarkNormalization{
|
|
Status: "full",
|
|
},
|
|
})
|
|
|
|
for _, needle := range []string{
|
|
"gpu-metrics.csv",
|
|
"gpu-metrics.html",
|
|
"gpu-burn.log",
|
|
} {
|
|
if !strings.Contains(report, needle) {
|
|
t.Fatalf("report missing %q\n%s", needle, report)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreBenchmarkGPUIgnoresDisabledPrecisions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
score := scoreBenchmarkGPUResult(BenchmarkGPUResult{
|
|
PrecisionSteady: []BenchmarkPrecisionSteadyPhase{
|
|
{Precision: "fp16", WeightedTeraOpsPerSec: 100},
|
|
{Precision: "fp64", WeightedTeraOpsPerSec: 999},
|
|
{Precision: "fp4", WeightedTeraOpsPerSec: 999},
|
|
},
|
|
PrecisionResults: []BenchmarkPrecisionResult{
|
|
{Category: "fp32_tf32", Supported: true, WeightedTeraOpsPerSec: 50},
|
|
{Category: "fp64", Supported: true, WeightedTeraOpsPerSec: 999},
|
|
{Category: "fp4", Supported: true, WeightedTeraOpsPerSec: 999},
|
|
},
|
|
})
|
|
|
|
if score.SyntheticScore != 100 {
|
|
t.Fatalf("SyntheticScore=%f want 100", score.SyntheticScore)
|
|
}
|
|
if score.MixedScore != 50 {
|
|
t.Fatalf("MixedScore=%f want 50", score.MixedScore)
|
|
}
|
|
}
|
|
|
|
func TestEnrichGPUInfoWithNvidiaSMIQ(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
nvsmiQ := []byte(`
|
|
GPU 00000000:4E:00.0
|
|
Product Name : NVIDIA RTX PRO 6000 Blackwell Server Edition
|
|
Min Power Limit : 200.00 W
|
|
Max Power Limit : 600.00 W
|
|
Default Power Limit : 575.00 W
|
|
Current Power Limit : 560.00 W
|
|
Clocks
|
|
Graphics : 2422 MHz
|
|
Memory : 12481 MHz
|
|
Max Clocks
|
|
Graphics : 2430 MHz
|
|
SM : 2430 MHz
|
|
Memory : 12481 MHz
|
|
Video : 2107 MHz
|
|
|
|
GPU 00000000:4F:00.0
|
|
Product Name : NVIDIA RTX PRO 6000 Blackwell Server Edition
|
|
Max Clocks
|
|
Graphics : 2430 MHz
|
|
Memory : 12481 MHz
|
|
`)
|
|
|
|
infoByIndex := map[int]benchmarkGPUInfo{
|
|
0: {Index: 0, BusID: "00000000:4E:00.0"},
|
|
1: {Index: 1, BusID: "00000000:4F:00.0"},
|
|
}
|
|
|
|
enrichGPUInfoWithNvidiaSMIQ(infoByIndex, nvsmiQ)
|
|
|
|
if infoByIndex[0].MaxGraphicsClockMHz != 2430 {
|
|
t.Errorf("GPU 0 MaxGraphicsClockMHz = %v, want 2430", infoByIndex[0].MaxGraphicsClockMHz)
|
|
}
|
|
if infoByIndex[0].MaxMemoryClockMHz != 12481 {
|
|
t.Errorf("GPU 0 MaxMemoryClockMHz = %v, want 12481", infoByIndex[0].MaxMemoryClockMHz)
|
|
}
|
|
if infoByIndex[1].MaxGraphicsClockMHz != 2430 {
|
|
t.Errorf("GPU 1 MaxGraphicsClockMHz = %v, want 2430", infoByIndex[1].MaxGraphicsClockMHz)
|
|
}
|
|
if infoByIndex[1].MaxMemoryClockMHz != 12481 {
|
|
t.Errorf("GPU 1 MaxMemoryClockMHz = %v, want 12481", infoByIndex[1].MaxMemoryClockMHz)
|
|
}
|
|
if infoByIndex[0].MinPowerLimitW != 200 {
|
|
t.Errorf("GPU 0 MinPowerLimitW = %v, want 200", infoByIndex[0].MinPowerLimitW)
|
|
}
|
|
if infoByIndex[0].MaxPowerLimitW != 600 {
|
|
t.Errorf("GPU 0 MaxPowerLimitW = %v, want 600", infoByIndex[0].MaxPowerLimitW)
|
|
}
|
|
if infoByIndex[0].DefaultPowerLimitW != 575 {
|
|
t.Errorf("GPU 0 DefaultPowerLimitW = %v, want 575", infoByIndex[0].DefaultPowerLimitW)
|
|
}
|
|
if infoByIndex[0].PowerLimitW != 560 {
|
|
t.Errorf("GPU 0 PowerLimitW = %v, want 560", infoByIndex[0].PowerLimitW)
|
|
}
|
|
}
|
|
|
|
func TestEnrichGPUInfoWithNvidiaSMIQSkipsPopulated(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
nvsmiQ := []byte(`
|
|
GPU 00000000:4E:00.0
|
|
Min Power Limit : 100.00 W
|
|
Max Power Limit : 900.00 W
|
|
Max Clocks
|
|
Graphics : 9999 MHz
|
|
Memory : 9999 MHz
|
|
`)
|
|
// Already populated — must not be overwritten.
|
|
infoByIndex := map[int]benchmarkGPUInfo{
|
|
0: {
|
|
Index: 0,
|
|
BusID: "00000000:4E:00.0",
|
|
MaxGraphicsClockMHz: 2430,
|
|
MaxMemoryClockMHz: 12481,
|
|
MinPowerLimitW: 200,
|
|
MaxPowerLimitW: 600,
|
|
},
|
|
}
|
|
|
|
enrichGPUInfoWithNvidiaSMIQ(infoByIndex, nvsmiQ)
|
|
|
|
if infoByIndex[0].MaxGraphicsClockMHz != 2430 {
|
|
t.Errorf("expected existing value to be preserved, got %v", infoByIndex[0].MaxGraphicsClockMHz)
|
|
}
|
|
if infoByIndex[0].MinPowerLimitW != 200 {
|
|
t.Errorf("expected existing min power limit to be preserved, got %v", infoByIndex[0].MinPowerLimitW)
|
|
}
|
|
}
|