From f1e43b2593636f837ed0351cbd0d872db5655351 Mon Sep 17 00:00:00 2001 From: henrygd Date: Mon, 26 May 2025 01:08:03 -0400 Subject: [PATCH] scale fractional temperature values to reasonable Celsius values (#688) --- beszel/internal/agent/sensors.go | 20 +++++++ beszel/internal/agent/sensors_test.go | 82 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/beszel/internal/agent/sensors.go b/beszel/internal/agent/sensors.go index a82bf69..384bccc 100644 --- a/beszel/internal/agent/sensors.go +++ b/beszel/internal/agent/sensors.go @@ -89,6 +89,10 @@ func (a *Agent) updateTemperatures(systemStats *system.Stats) { systemStats.Temperatures = make(map[string]float64, len(temps)) for i, sensor := range temps { + // scale temperature + if sensor.Temperature != 0 && sensor.Temperature < 1 { + sensor.Temperature = scaleTemperature(sensor.Temperature) + } // skip if temperature is unreasonable if sensor.Temperature <= 0 || sensor.Temperature >= 200 { continue @@ -141,3 +145,19 @@ func isValidSensor(sensorName string, config *SensorConfig) bool { return config.isBlacklist } + +// scaleTemperature scales temperatures in fractional values to reasonable Celsius values +func scaleTemperature(temp float64) float64 { + if temp > 1 { + return temp + } + scaled100 := temp * 100 + scaled1000 := temp * 1000 + + if scaled100 >= 15 && scaled100 <= 95 { + return scaled100 + } else if scaled1000 >= 15 && scaled1000 <= 95 { + return scaled1000 + } + return scaled100 +} diff --git a/beszel/internal/agent/sensors_test.go b/beszel/internal/agent/sensors_test.go index 22e1a5c..85970ac 100644 --- a/beszel/internal/agent/sensors_test.go +++ b/beszel/internal/agent/sensors_test.go @@ -372,3 +372,85 @@ func TestNewSensorConfig(t *testing.T) { require.True(t, ok, "EnvMap should contain HostSysEnvKey") assert.Equal(t, "/test/path", sysPath) } + +func TestScaleTemperature(t *testing.T) { + tests := []struct { + name string + input float64 + expected float64 + desc string + }{ + // Normal temperatures (no scaling needed) + {"normal_cpu_temp", 45.0, 45.0, "Normal CPU temperature"}, + {"normal_room_temp", 25.0, 25.0, "Normal room temperature"}, + {"high_cpu_temp", 85.0, 85.0, "High CPU temperature"}, + // Zero temperature + {"zero_temp", 0.0, 0.0, "Zero temperature"}, + // Fractional values that should use 100x scaling + {"fractional_45c", 0.45, 45.0, "0.45 should become 45°C (100x)"}, + {"fractional_25c", 0.25, 25.0, "0.25 should become 25°C (100x)"}, + {"fractional_60c", 0.60, 60.0, "0.60 should become 60°C (100x)"}, + {"fractional_75c", 0.75, 75.0, "0.75 should become 75°C (100x)"}, + {"fractional_30c", 0.30, 30.0, "0.30 should become 30°C (100x)"}, + // Fractional values that should use 1000x scaling + {"millifractional_45c", 0.045, 45.0, "0.045 should become 45°C (1000x)"}, + {"millifractional_25c", 0.025, 25.0, "0.025 should become 25°C (1000x)"}, + {"millifractional_60c", 0.060, 60.0, "0.060 should become 60°C (1000x)"}, + {"millifractional_75c", 0.075, 75.0, "0.075 should become 75°C (1000x)"}, + {"millifractional_35c", 0.035, 35.0, "0.035 should become 35°C (1000x)"}, + // Edge cases - values outside reasonable range + {"very_low_fractional", 0.01, 1.0, "0.01 should default to 100x scaling (1°C)"}, + {"very_high_fractional", 0.99, 99.0, "0.99 should default to 100x scaling (99°C)"}, + {"extremely_low", 0.001, 0.1, "0.001 should default to 100x scaling (0.1°C)"}, + // Boundary cases around the reasonable range (15-95°C) + {"boundary_low_100x", 0.15, 15.0, "0.15 should use 100x scaling (15°C)"}, + {"boundary_high_100x", 0.95, 95.0, "0.95 should use 100x scaling (95°C)"}, + {"boundary_low_1000x", 0.015, 15.0, "0.015 should use 1000x scaling (15°C)"}, + {"boundary_high_1000x", 0.095, 95.0, "0.095 should use 1000x scaling (95°C)"}, + // Values just outside reasonable range + {"just_below_range_100x", 0.14, 14.0, "0.14 should default to 100x (14°C)"}, + {"just_above_range_100x", 0.96, 96.0, "0.96 should default to 100x (96°C)"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := scaleTemperature(tt.input) + assert.InDelta(t, tt.expected, result, 0.001, + "scaleTemperature(%v) = %v, expected %v (%s)", + tt.input, result, tt.expected, tt.desc) + }) + } +} + +func TestScaleTemperatureLogic(t *testing.T) { + // Test the logic flow for ambiguous cases + t.Run("prefers_100x_when_both_valid", func(t *testing.T) { + // 0.5 could be 50°C (100x) or 500°C (1000x) + // Should prefer 100x since it's tried first and is in range + result := scaleTemperature(0.5) + expected := 50.0 + assert.InDelta(t, expected, result, 0.001, + "scaleTemperature(0.5) = %v, expected %v (should prefer 100x scaling)", + result, expected) + }) + + t.Run("uses_1000x_when_100x_too_low", func(t *testing.T) { + // 0.05 -> 5°C (100x, too low) or 50°C (1000x, in range) + // Should use 1000x since 100x is below reasonable range + result := scaleTemperature(0.05) + expected := 50.0 + assert.InDelta(t, expected, result, 0.001, + "scaleTemperature(0.05) = %v, expected %v (should use 1000x scaling)", + result, expected) + }) + + t.Run("defaults_to_100x_when_both_invalid", func(t *testing.T) { + // 0.005 -> 0.5°C (100x, too low) or 5°C (1000x, too low) + // Should default to 100x scaling + result := scaleTemperature(0.005) + expected := 0.5 + assert.InDelta(t, expected, result, 0.001, + "scaleTemperature(0.005) = %v, expected %v (should default to 100x)", + result, expected) + }) +}