Implements temperature monitoring in pulse-host-agent to support Docker-in-VM deployments where the sensor proxy socket cannot cross VM boundaries. Changes: - Create internal/sensors package with local collection and parsing - Add temperature collection to host agent (Linux only, best-effort) - Support CPU package/core, NVMe, and GPU temperature sensors - Update TEMPERATURE_MONITORING.md with Docker-in-VM setup instructions - Update HOST_AGENT.md to document temperature feature The host agent now automatically collects temperature data on Linux systems with lm-sensors installed. This provides an alternative path for temperature monitoring when running Pulse in a VM, avoiding the unix socket limitation. Temperature collection is best-effort and fails gracefully if lm-sensors is not available, ensuring other metrics continue to be reported. Related to #661
47 lines
1.5 KiB
Go
47 lines
1.5 KiB
Go
package sensors
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// CollectLocal reads sensor data from the local machine using lm-sensors.
|
|
// Returns the raw JSON output from `sensors -j` or an error if sensors is not available.
|
|
func CollectLocal(ctx context.Context) (string, error) {
|
|
// Check if sensors command exists
|
|
if _, err := exec.LookPath("sensors"); err != nil {
|
|
return "", fmt.Errorf("lm-sensors not installed: %w", err)
|
|
}
|
|
|
|
// Create context with timeout
|
|
cmdCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
// Run sensors -j command
|
|
// sensors exits non-zero when optional subfeatures fail; "|| true" keeps the JSON for parsing
|
|
cmd := exec.CommandContext(cmdCtx, "sh", "-c", "sensors -j 2>/dev/null || true")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to execute sensors: %w", err)
|
|
}
|
|
|
|
outputStr := strings.TrimSpace(string(output))
|
|
if outputStr == "" || outputStr == "{}" {
|
|
// Try Raspberry Pi temperature method as fallback
|
|
cmd = exec.CommandContext(cmdCtx, "cat", "/sys/class/thermal/thermal_zone0/temp")
|
|
if rpiOutput, rpiErr := cmd.Output(); rpiErr == nil {
|
|
rpiTemp := strings.TrimSpace(string(rpiOutput))
|
|
if rpiTemp != "" {
|
|
// Convert to pseudo-sensors format for compatibility
|
|
// Raspberry Pi reports in millidegrees Celsius
|
|
return fmt.Sprintf(`{"cpu_thermal-virtual-0":{"temp1":{"temp1_input":%s}}}`, rpiTemp), nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("sensors returned empty output")
|
|
}
|
|
|
|
return outputStr, nil
|
|
}
|