feat: The Other Dude v9.0.1 — full-featured email system
ci: add GitHub Pages deployment workflow for docs site Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
110
poller/internal/device/health.go
Normal file
110
poller/internal/device/health.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
routeros "github.com/go-routeros/routeros/v3"
|
||||
)
|
||||
|
||||
// HealthMetrics holds system resource metrics collected from a RouterOS device.
|
||||
// String fields match the raw RouterOS API values so the subscriber can parse
|
||||
// and validate them before inserting into TimescaleDB.
|
||||
type HealthMetrics struct {
|
||||
CPULoad string `json:"cpu_load"`
|
||||
FreeMemory string `json:"free_memory"`
|
||||
TotalMemory string `json:"total_memory"`
|
||||
FreeDisk string `json:"free_disk"`
|
||||
TotalDisk string `json:"total_disk"`
|
||||
Temperature string `json:"temperature"` // empty string if device has no sensor
|
||||
}
|
||||
|
||||
// CollectHealth gathers system health metrics for a RouterOS device.
|
||||
//
|
||||
// It combines data already present in DeviceInfo (CPU, memory) with additional
|
||||
// disk stats from /system/resource/print and temperature from /system/health/print.
|
||||
//
|
||||
// Temperature handling:
|
||||
// - RouterOS v7: /system/health/print returns rows with name/value columns;
|
||||
// looks for "cpu-temperature" then "board-temperature" as a fallback.
|
||||
// - RouterOS v6: /system/health/print returns a flat map; looks for
|
||||
// "cpu-temperature" key directly.
|
||||
// - If the command fails or no temperature key is found, Temperature is set to "".
|
||||
func CollectHealth(client *routeros.Client, info DeviceInfo) (HealthMetrics, error) {
|
||||
health := HealthMetrics{
|
||||
CPULoad: info.CPULoad,
|
||||
FreeMemory: info.FreeMemory,
|
||||
TotalMemory: info.TotalMemory,
|
||||
}
|
||||
|
||||
// Collect disk stats (not included in the default /system/resource/print proplist
|
||||
// used by DetectVersion, so we query explicitly here).
|
||||
diskReply, err := client.Run(
|
||||
"/system/resource/print",
|
||||
"=.proplist=free-hdd-space,total-hdd-space",
|
||||
)
|
||||
if err != nil {
|
||||
slog.Warn("could not collect disk stats", "error", err)
|
||||
} else if len(diskReply.Re) > 0 {
|
||||
m := diskReply.Re[0].Map
|
||||
health.FreeDisk = m["free-hdd-space"]
|
||||
health.TotalDisk = m["total-hdd-space"]
|
||||
}
|
||||
|
||||
// Collect temperature from /system/health/print.
|
||||
// This command may not exist on all devices, so errors are non-fatal.
|
||||
health.Temperature = collectTemperature(client, info.MajorVersion)
|
||||
|
||||
return health, nil
|
||||
}
|
||||
|
||||
// collectTemperature queries /system/health/print and extracts the temperature
|
||||
// reading. Returns an empty string if the device has no temperature sensor or
|
||||
// the command is not supported.
|
||||
func collectTemperature(client *routeros.Client, majorVersion int) string {
|
||||
reply, err := client.Run("/system/health/print")
|
||||
if err != nil {
|
||||
slog.Debug("temperature collection not available", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(reply.Re) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// RouterOS v7 returns rows with "name" and "value" columns.
|
||||
// RouterOS v6 returns a flat map in a single sentence.
|
||||
if majorVersion >= 7 {
|
||||
// v7: iterate rows looking for known temperature keys.
|
||||
var fallback string
|
||||
for _, sentence := range reply.Re {
|
||||
m := sentence.Map
|
||||
name := m["name"]
|
||||
value := m["value"]
|
||||
if name == "cpu-temperature" {
|
||||
return value
|
||||
}
|
||||
if name == "board-temperature" {
|
||||
fallback = value
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// v6 (or unknown version): flat map — look for cpu-temperature key directly.
|
||||
m := reply.Re[0].Map
|
||||
if temp, ok := m["cpu-temperature"]; ok {
|
||||
return temp
|
||||
}
|
||||
if temp, ok := m["board-temperature"]; ok {
|
||||
return temp
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// collectHealthError returns an error for CollectHealth callers when the
|
||||
// primary resource query fails completely.
|
||||
func collectHealthError(err error) error {
|
||||
return fmt.Errorf("collecting health metrics: %w", err)
|
||||
}
|
||||
Reference in New Issue
Block a user