simplify the driver interface

This commit is contained in:
alu
2026-08-07 09:29:17 -07:00
parent b99bb45877
commit 31c409a94c
13 changed files with 181 additions and 197 deletions
+5 -5
View File
@@ -2,7 +2,7 @@ package app
import ( import (
"finally-a-monolithic-linux-hw-monitor/app/mem" "finally-a-monolithic-linux-hw-monitor/app/mem"
proc "finally-a-monolithic-linux-hw-monitor/app/proc" "finally-a-monolithic-linux-hw-monitor/app/proc"
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
@@ -25,8 +25,8 @@ func Run() {
os.Exit(0) os.Exit(0)
}() }()
p, _ := proc.GetProc() proc, _ := proc.GetProc()
m, _ := mem.GetMem() mem, _ := mem.GetMem()
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() defer ticker.Stop()
@@ -35,8 +35,8 @@ func Run() {
// Move cursor to top-left (0,0) without clearing full buffer (flicker-free) // Move cursor to top-left (0,0) without clearing full buffer (flicker-free)
fmt.Fprint(console, "\033[H") fmt.Fprint(console, "\033[H")
proc.RenderProc(console, &p) proc.RenderProc(console)
mem.RenderMem(console, &m) mem.Render(console)
fmt.Fprint(console, "\nPress Ctrl+C to exit.\033[K\n") fmt.Fprint(console, "\nPress Ctrl+C to exit.\033[K\n")
fmt.Print(console.String()) fmt.Print(console.String())
+8
View File
@@ -0,0 +1,8 @@
package common
import "strings"
type Driver interface {
Read() (any, error)
Render(*strings.Builder) error
}
+27 -3
View File
@@ -10,12 +10,36 @@ import (
type DmiDecodeDriver struct{} type DmiDecodeDriver struct{}
func NewDmiDecodeDriver() *DmiDecodeDriver { func NewDmiDecodeDriver() (*DmiDecodeDriver, error) {
return &DmiDecodeDriver{} r := DmiDecodeDriver{}
_, err := r.Read()
return &r, err
}
func (r *DmiDecodeDriver) Render(console *strings.Builder) error {
h, err := r.Read()
if err != nil {
return err
}
hardware := h.(MemoryHardware)
for i := range len(hardware.ModelNames) {
fmt.Fprintf(console, "--- RAM Module %d ---\n", i)
modelName := hardware.ModelNames[i]
fmt.Fprintf(console, "%-16s | %-16s \033[K\n", "Model Name", modelName)
generation := hardware.Generations[i]
speed := hardware.Speeds[i]
fmt.Fprintf(console, "%-16s | %s@%s \033[K\n", "Speed", generation, speed)
formFactor := hardware.FormFactors[i]
fmt.Fprintf(console, "%-16s | %s \033[K\n", "Form Factor", formFactor)
}
return nil
} }
// Parses dmidecode to extract physical hardware limits, slot counts, and module details // Parses dmidecode to extract physical hardware limits, slot counts, and module details
func (r *DmiDecodeDriver) ReadHardwareSensors() (MemoryHardware, error) { func (r *DmiDecodeDriver) Read() (any, error) {
hw := MemoryHardware{} hw := MemoryHardware{}
// Query Memory Array (Type 16) for Max Capacity and Slot Count // Query Memory Array (Type 16) for Max Capacity and Slot Count
-8
View File
@@ -8,10 +8,6 @@ type MemoryUsage struct {
UsedPercent float64 UsedPercent float64
} }
type UsageDriver interface {
ReadUsageSensors() (MemoryUsage, error)
}
// MemoryHardware holds physical RAM slot and module details // MemoryHardware holds physical RAM slot and module details
type MemoryHardware struct { type MemoryHardware struct {
MaxCapacityGB float64 MaxCapacityGB float64
@@ -22,7 +18,3 @@ type MemoryHardware struct {
FormFactors []string FormFactors []string
ModelNames []string ModelNames []string
} }
type HardwareDriver interface {
ReadHardwareSensors() (MemoryHardware, error)
}
+22 -3
View File
@@ -2,6 +2,7 @@ package drivers
import ( import (
"bufio" "bufio"
"fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@@ -9,12 +10,30 @@ import (
type MemInfoDriver struct{} type MemInfoDriver struct{}
func NewMemInfoDriver() *MemInfoDriver { func NewMemInfoDriver() (*MemInfoDriver, error) {
return &MemInfoDriver{} r := MemInfoDriver{}
_, err := r.Read()
return &r, err
}
func (r *MemInfoDriver) Render(console *strings.Builder) error {
u, err := r.Read()
if err != nil {
return err
}
usage := u.(MemoryUsage)
fmt.Fprintf(console, "--- Usage ---\n")
fmt.Fprintf(console, "%-16s | %.2f GiB\033[K\n", "Total", usage.TotalGB)
fmt.Fprintf(console, "%-16s | %.2f GiB (%.2f%%) \033[K\n", "Used", usage.UsedGB, usage.UsedPercent)
fmt.Fprintf(console, "%-16s | %.2f GiB\033[K\n", "Avail", usage.AvailableGB)
return nil
} }
// Read /proc/meminfo for active system memory statistics // Read /proc/meminfo for active system memory statistics
func (r *MemInfoDriver) ReadUsageSensors() (MemoryUsage, error) { func (r *MemInfoDriver) Read() (any, error) {
usage := MemoryUsage{} usage := MemoryUsage{}
file, err := os.Open("/proc/meminfo") file, err := os.Open("/proc/meminfo")
if err != nil { if err != nil {
+16 -49
View File
@@ -1,69 +1,36 @@
package mem package mem
import ( import (
"finally-a-monolithic-linux-hw-monitor/app/common"
"finally-a-monolithic-linux-hw-monitor/app/mem/drivers" "finally-a-monolithic-linux-hw-monitor/app/mem/drivers"
"fmt" "fmt"
"strings" "strings"
) )
func RenderMem(console *strings.Builder, mem *Mem) { func (mem *Mem) Render(console *strings.Builder) {
fmt.Fprintf(console, "=== %s ===\n", "Memory") fmt.Fprintf(console, "=== %s ===\n", "Memory")
RenderUsageSensors(console, mem) for _, driver := range mem.drivers {
RenderHardwareSensors(console, mem) driver.Render(console)
}
func RenderUsageSensors(console *strings.Builder, mem *Mem) {
usage, _ := mem.SensorReader.ReadUsageSensors()
fmt.Fprintf(console, "--- Usage ---\n")
fmt.Fprintf(console, "%-16s | %.2f GiB\033[K\n", "Total", usage.TotalGB)
fmt.Fprintf(console, "%-16s | %.2f GiB (%.2f%%) \033[K\n", "Used", usage.UsedGB, usage.UsedPercent)
fmt.Fprintf(console, "%-16s | %.2f GiB\033[K\n", "Avail", usage.AvailableGB)
}
func RenderHardwareSensors(console *strings.Builder, mem *Mem) {
hardware, _ := mem.SensorReader.ReadHardwareSensors()
for i := range len(hardware.ModelNames) {
fmt.Fprintf(console, "--- RAM Module %d ---\n", i)
modelName := hardware.ModelNames[i]
fmt.Fprintf(console, "%-16s | %-16s \033[K\n", "Model Name", modelName)
generation := hardware.Generations[i]
speed := hardware.Speeds[i]
fmt.Fprintf(console, "%-16s | %s@%s \033[K\n", "Speed", generation, speed)
formFactor := hardware.FormFactors[i]
fmt.Fprintf(console, "%-16s | %s \033[K\n", "Form Factor", formFactor)
} }
} }
type Mem struct { type Mem struct {
SensorReader MemSensorReader drivers []common.Driver
} }
func GetMem() (Mem, error) { func GetMem() (Mem, error) {
memInfoDriver := drivers.NewMemInfoDriver() mem := Mem{}
dmiDecodeDriver := drivers.NewDmiDecodeDriver() mem.drivers = []common.Driver{}
reader := &StandardMemSensorReader{
UsageReader: memInfoDriver, meminfo, err := drivers.NewMemInfoDriver()
HardwareReader: dmiDecodeDriver, if err == nil {
mem.drivers = append(mem.drivers, meminfo)
} }
return Mem{
SensorReader: reader,
}, nil
}
type MemSensorReader interface { dmidecode, err := drivers.NewDmiDecodeDriver()
ReadUsageSensors() (drivers.MemoryUsage, error) if err == nil {
ReadHardwareSensors() (drivers.MemoryHardware, error) mem.drivers = append(mem.drivers, dmidecode)
} }
type StandardMemSensorReader struct { return mem, nil
UsageReader drivers.UsageDriver
HardwareReader drivers.HardwareDriver
}
func (r *StandardMemSensorReader) ReadUsageSensors() (drivers.MemoryUsage, error) {
return r.UsageReader.ReadUsageSensors()
}
func (r *StandardMemSensorReader) ReadHardwareSensors() (drivers.MemoryHardware, error) {
return r.HardwareReader.ReadHardwareSensors()
} }
-13
View File
@@ -6,27 +6,14 @@ type TemperatureSensor struct {
TempC float64 TempC float64
} }
type TemperatureDriver interface {
ReadTemperatureSensors() ([]TemperatureSensor, error)
}
type PowerSensor struct { type PowerSensor struct {
path string path string
Name string Name string
Watts float64 Watts float64
} }
type PowerDriver interface {
ReadPowerSensors() ([]PowerSensor, error)
}
type ClockSensor struct { type ClockSensor struct {
path string path string
number int number int
Name string Name string
FrequencyGHz float64 FrequencyGHz float64
} }
type ClockDriver interface {
ReadClockSensors() ([]ClockSensor, error)
}
+22 -3
View File
@@ -13,11 +13,11 @@ type HwmonDriver struct {
hwmonDriver string hwmonDriver string
} }
func NewHwmonDriver(expectedDrivers []string) *HwmonDriver { func NewHwmonDriver(expectedDrivers []string) (*HwmonDriver, error) {
// Locate hwmon path // Locate hwmon path
dirs, err := filepath.Glob("/sys/class/hwmon/hwmon*") dirs, err := filepath.Glob("/sys/class/hwmon/hwmon*")
if err != nil { if err != nil {
return nil return nil, err
} }
for _, dir := range dirs { for _, dir := range dirs {
@@ -29,16 +29,35 @@ func NewHwmonDriver(expectedDrivers []string) *HwmonDriver {
return &HwmonDriver{ return &HwmonDriver{
hwmonDir: dir, hwmonDir: dir,
hwmonDriver: driver, hwmonDriver: driver,
}, nil
} }
} }
} }
} }
return nil, fmt.Errorf("could not find %#v in /sys/class/hwmon", expectedDrivers)
}
func (r *HwmonDriver) Render(console *strings.Builder) error {
t, err := r.Read()
if err != nil {
return err
}
temps := t.([]TemperatureSensor)
fmt.Fprint(console, "--- Temperature ---\n")
for _, sensor := range temps {
tempC := sensor.TempC
tempF := (tempC * 9 / 5) + 32
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f °C (%.3f °F)\033[K\n", sensor.Name, tempC, tempF)
} }
return nil return nil
} }
func (r *HwmonDriver) ReadTemperatureSensors() ([]TemperatureSensor, error) { func (r *HwmonDriver) Read() (any, error) {
inputs, err := filepath.Glob(filepath.Join(r.hwmonDir, "temp*_input")) inputs, err := filepath.Glob(filepath.Join(r.hwmonDir, "temp*_input"))
if err != nil || len(inputs) == 0 { if err != nil || len(inputs) == 0 {
return nil, fmt.Errorf("no temperature inputs found in %s", r.hwmonDir) return nil, fmt.Errorf("no temperature inputs found in %s", r.hwmonDir)
+22 -4
View File
@@ -20,14 +20,32 @@ type RaplDriver struct {
energyState map[string]EnergyState energyState map[string]EnergyState
} }
func NewRaplDriver() *RaplDriver { func NewRaplDriver() (*RaplDriver, error) {
return &RaplDriver{ r := RaplDriver{
energyState: map[string]EnergyState{}, energyState: map[string]EnergyState{},
} }
_, err := r.Read()
return &r, err
} }
func (r *RaplDriver) ReadPowerSensors() ([]PowerSensor, error) { func (r *RaplDriver) Render(console *strings.Builder) error {
fmt.Fprint(console, "--- Power ---\n")
p, err := r.Read()
if err != nil {
return err
}
power := p.([]PowerSensor)
for _, sensor := range power {
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f W\033[K\n", sensor.Name, sensor.Watts)
}
return nil
}
func (r *RaplDriver) Read() (any, error) {
powerSensors := []PowerSensor{} powerSensors := []PowerSensor{}
patterns := []string{ patterns := []string{
+22 -3
View File
@@ -11,11 +11,30 @@ import (
type SysDriver struct{} type SysDriver struct{}
func NewSysDriver() *SysDriver { func NewSysDriver() (*SysDriver, error) {
return &SysDriver{} r := SysDriver{}
_, err := r.Read()
return &r, err
} }
func (r *SysDriver) ReadClockSensors() ([]ClockSensor, error) { func (r *SysDriver) Render(console *strings.Builder) error {
fmt.Fprint(console, "--- Clock Frequency ---\n")
c, err := r.Read()
if err != nil {
return err
}
clock := c.([]ClockSensor)
for _, sensor := range clock {
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f GHz\033[K\n", sensor.Name, sensor.FrequencyGHz)
}
return nil
}
func (r *SysDriver) Read() (any, error) {
// Glob directly targets scaling_cur_freq for all numerical CPU core paths // Glob directly targets scaling_cur_freq for all numerical CPU core paths
freqFiles, err := filepath.Glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_cur_freq") freqFiles, err := filepath.Glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_cur_freq")
if err != nil { if err != nil {
+9 -45
View File
@@ -2,60 +2,24 @@ package proc
import ( import (
"bufio" "bufio"
"finally-a-monolithic-linux-hw-monitor/app/proc/drivers" "finally-a-monolithic-linux-hw-monitor/app/common"
"fmt" "fmt"
"os" "os"
"strings" "strings"
) )
func RenderProc(console *strings.Builder, proc *Proc) { func (proc *Proc) RenderProc(console *strings.Builder) {
fmt.Fprintf(console, "=== %s ===\n", proc.Model()) fmt.Fprintf(console, "=== %s ===\n", proc.Model())
for _, driver := range proc.drivers {
RenderTemperatureSensors(console, proc) driver.Render(console)
RenderPowerSensors(console, proc)
RenderClockSensors(console, proc)
}
func RenderTemperatureSensors(console *strings.Builder, proc *Proc) {
temps, _ := proc.SensorReader.ReadTemperatureSensors()
fmt.Fprint(console, "--- Temperature ---\n")
for _, sensor := range temps {
tempC := sensor.TempC
tempF := (tempC * 9 / 5) + 32
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f °C (%.3f °F)\033[K\n", sensor.Name, tempC, tempF)
}
}
func RenderPowerSensors(console *strings.Builder, proc *Proc) {
fmt.Fprint(console, "--- Power ---\n")
power, _ := proc.SensorReader.ReadPowerSensors()
for _, sensor := range power {
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f W\033[K\n", sensor.Name, sensor.Watts)
}
}
func RenderClockSensors(console *strings.Builder, proc *Proc) {
fmt.Fprint(console, "--- Clock Frequency ---\n")
power, _ := proc.SensorReader.ReadClockSensors()
for _, sensor := range power {
// \033[K clears from cursor to end of line (prevents lingering chars)
fmt.Fprintf(console, "%-16s | %.2f GHz\033[K\n", sensor.Name, sensor.FrequencyGHz)
} }
} }
type Proc struct { type Proc struct {
SensorReader ProcSensorReader drivers []common.Driver
Info map[string]string Info map[string]string
} }
type ProcSensorReader interface {
ReadTemperatureSensors() ([]drivers.TemperatureSensor, error)
ReadPowerSensors() ([]drivers.PowerSensor, error)
ReadClockSensors() ([]drivers.ClockSensor, error)
}
const ( const (
VendorIntel string = "GenuineIntel" VendorIntel string = "GenuineIntel"
VendorAMD string = "AuthenticAMD" VendorAMD string = "AuthenticAMD"
@@ -96,12 +60,12 @@ func GetProc() (Proc, error) {
switch vendor { switch vendor {
case VendorIntel: case VendorIntel:
intelReader, err := NewIntelReader() drivers, err := NewIntelDrivers()
proc.SensorReader = intelReader proc.drivers = drivers
return proc, err return proc, err
case VendorAMD: case VendorAMD:
amdReader, err := NewAMDReader() drivers, err := NewAMDDrivers()
proc.SensorReader = amdReader proc.drivers = drivers
return proc, err return proc, err
default: default:
return proc, fmt.Errorf("proc type %s is not supported", vendor) return proc, fmt.Errorf("proc type %s is not supported", vendor)
+13 -29
View File
@@ -1,47 +1,31 @@
package proc package proc
import ( import (
"finally-a-monolithic-linux-hw-monitor/app/common"
driver "finally-a-monolithic-linux-hw-monitor/app/proc/drivers" driver "finally-a-monolithic-linux-hw-monitor/app/proc/drivers"
"fmt" "fmt"
) )
type AMDReader struct { func NewAMDDrivers() ([]common.Driver, error) {
temperatureDriver driver.TemperatureDriver drivers := []common.Driver{}
powerDriver driver.PowerDriver
clockDriver driver.ClockDriver
}
func NewAMDReader() (*AMDReader, error) { temperatureDriver, err := driver.NewHwmonDriver([]string{"k10temp", "zenpower"})
temperatureDriver := driver.NewHwmonDriver([]string{"k10temp", "zenpower"}) if err != nil {
if temperatureDriver == nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, temperatureDriver)
powerDriver := driver.NewRaplDriver() powerDriver, err := driver.NewRaplDriver()
if powerDriver == nil { if err != nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, powerDriver)
clockDriver := driver.NewSysDriver() clockDriver, err := driver.NewSysDriver()
if clockDriver == nil { if err != nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, clockDriver)
return &AMDReader{ return drivers, nil
temperatureDriver: temperatureDriver,
powerDriver: powerDriver,
clockDriver: clockDriver,
}, nil
}
func (r *AMDReader) ReadTemperatureSensors() ([]driver.TemperatureSensor, error) {
return r.temperatureDriver.ReadTemperatureSensors()
}
func (r *AMDReader) ReadPowerSensors() ([]driver.PowerSensor, error) {
return r.powerDriver.ReadPowerSensors()
}
func (r *AMDReader) ReadClockSensors() ([]driver.ClockSensor, error) {
return r.clockDriver.ReadClockSensors()
} }
+13 -30
View File
@@ -1,47 +1,30 @@
package proc package proc
import ( import (
"finally-a-monolithic-linux-hw-monitor/app/common"
driver "finally-a-monolithic-linux-hw-monitor/app/proc/drivers" driver "finally-a-monolithic-linux-hw-monitor/app/proc/drivers"
"fmt" "fmt"
) )
type IntelReader struct { func NewIntelDrivers() ([]common.Driver, error) {
temperatureDriver driver.TemperatureDriver drivers := []common.Driver{}
powerDriver driver.PowerDriver temperatureDriver, err := driver.NewHwmonDriver([]string{"coretemp"})
clockDriver driver.ClockDriver if err != nil {
}
func NewIntelReader() (*IntelReader, error) {
temperatureDriver := driver.NewHwmonDriver([]string{"coretemp"})
if temperatureDriver == nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, temperatureDriver)
powerDriver := driver.NewRaplDriver() powerDriver, err := driver.NewRaplDriver()
if powerDriver == nil { if err != nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, powerDriver)
clockDriver := driver.NewSysDriver() clockDriver, err := driver.NewSysDriver()
if clockDriver == nil { if err != nil {
return nil, fmt.Errorf("could not initialize hwmon temperature driver") return nil, fmt.Errorf("could not initialize hwmon temperature driver")
} }
drivers = append(drivers, clockDriver)
return &IntelReader{ return drivers, nil
temperatureDriver: temperatureDriver,
powerDriver: powerDriver,
clockDriver: clockDriver,
}, nil
}
func (r *IntelReader) ReadTemperatureSensors() ([]driver.TemperatureSensor, error) {
return r.temperatureDriver.ReadTemperatureSensors()
}
func (r *IntelReader) ReadPowerSensors() ([]driver.PowerSensor, error) {
return r.powerDriver.ReadPowerSensors()
}
func (r *IntelReader) ReadClockSensors() ([]driver.ClockSensor, error) {
return r.clockDriver.ReadClockSensors()
} }