From ea5e79a46afe343b6d9673e6df82d26d52dc52ad Mon Sep 17 00:00:00 2001 From: Dottere Date: Mon, 5 Oct 2026 18:19:36 +0200 Subject: [PATCH] Testing for linux cpuinfo --- cmd/rpi5-health/main.go | 13 ++++- .../{system_check => memory}/linux_ram.go | 2 +- internal/{system_check => memory}/ram.go | 2 +- internal/{system_check => memory}/win_ram.go | 33 ++++++------ internal/processor/cpu.go | 34 +++++++++++++ internal/processor/linux_cpu.go | 50 +++++++++++++++++++ internal/processor/win_cpu.go | 48 ++++++++++++++++++ .../{system_check => windows_system}/win.go | 10 +++- 8 files changed, 172 insertions(+), 20 deletions(-) rename internal/{system_check => memory}/linux_ram.go (98%) rename internal/{system_check => memory}/ram.go (93%) rename internal/{system_check => memory}/win_ram.go (55%) create mode 100644 internal/processor/cpu.go create mode 100644 internal/processor/linux_cpu.go create mode 100644 internal/processor/win_cpu.go rename internal/{system_check => windows_system}/win.go (67%) diff --git a/cmd/rpi5-health/main.go b/cmd/rpi5-health/main.go index 09860e0..897de1e 100644 --- a/cmd/rpi5-health/main.go +++ b/cmd/rpi5-health/main.go @@ -4,14 +4,15 @@ import ( "log" "rpi5-health/internal/cli" "rpi5-health/internal/http_internal" - "rpi5-health/internal/system_check" + "rpi5-health/internal/memory" + "rpi5-health/internal/processor" ) func main() { srv := http_internal.NewAppServer() go srv.Start() - memInfo, _ := system_check.GetRamInformation() + memInfo, _ := memory.GetRamInformation() const GB = 1024 * 1024 * 1024 @@ -19,5 +20,13 @@ func main() { log.Printf("Available memory: %.2fGB\n", float64(memInfo.AvailableMemory)/GB) log.Printf("Used memory: %.2fGB\n", float64(memInfo.GetUsedMemory())/GB) + _, kernel, usage, idle := processor.GetCPUInformation().Usage() + + const HOUR = 60 * 60 + + log.Printf("Total time spent idling: %.2f%%\n", idle) + log.Printf("Total time spent in kernel mode: %.2f%%\n", kernel) + log.Printf("Total time spent in user mode: %.2f%%\n", usage) + cli.Listen() } diff --git a/internal/system_check/linux_ram.go b/internal/memory/linux_ram.go similarity index 98% rename from internal/system_check/linux_ram.go rename to internal/memory/linux_ram.go index d0465a6..e561bda 100644 --- a/internal/system_check/linux_ram.go +++ b/internal/memory/linux_ram.go @@ -1,6 +1,6 @@ //go:build linux -package system_check +package memory import ( "bufio" diff --git a/internal/system_check/ram.go b/internal/memory/ram.go similarity index 93% rename from internal/system_check/ram.go rename to internal/memory/ram.go index c1b90c1..6d12c00 100644 --- a/internal/system_check/ram.go +++ b/internal/memory/ram.go @@ -1,4 +1,4 @@ -package system_check +package memory type MemoryInformation struct { /* diff --git a/internal/system_check/win_ram.go b/internal/memory/win_ram.go similarity index 55% rename from internal/system_check/win_ram.go rename to internal/memory/win_ram.go index 4ed2e6a..12e3187 100644 --- a/internal/system_check/win_ram.go +++ b/internal/memory/win_ram.go @@ -1,29 +1,34 @@ //go:build windows -package system_check +package memory import ( "log" "unsafe" + "rpi5-health/internal/windows_system" + "golang.org/x/sys/windows" ) +type DWORD uint32 +type DWORDLONG uint64 + type memoryStatusEx struct { /* * Struct for storing memory status information. * * It gets populated via syscall. */ - dwLength uint32 - dwMemoryLoad uint32 - ullTotalPhys uint64 - ullAvailPhys uint64 - ullTotalPageFile uint64 - ullAvailPageFile uint64 - ullTotalVirtual uint64 - ullAvailVirtual uint64 - ullAvailExtendedVirtual uint64 + dwLength DWORD + dwMemoryLoad DWORD + ullTotalPhys DWORDLONG + ullAvailPhys DWORDLONG + ullTotalPageFile DWORDLONG + ullAvailPageFile DWORDLONG + ullTotalVirtual DWORDLONG + ullAvailVirtual DWORDLONG + ullAvailExtendedVirtual DWORDLONG } func getWinRamInfo(procGlobalMemoryStatusEx *windows.LazyProc) MemoryInformation { @@ -31,7 +36,7 @@ func getWinRamInfo(procGlobalMemoryStatusEx *windows.LazyProc) MemoryInformation // * Windows requires the dwLength field to be set to the struct's size // * before execution so it knows which OS struct version is being used. - mem.dwLength = uint32(unsafe.Sizeof(mem)) + mem.dwLength = DWORD(unsafe.Sizeof(mem)) ret, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&mem))) // exec syscall @@ -40,11 +45,11 @@ func getWinRamInfo(procGlobalMemoryStatusEx *windows.LazyProc) MemoryInformation } return MemoryInformation{ - SystemMemory: mem.ullTotalPhys, - AvailableMemory: mem.ullAvailPhys, + SystemMemory: uint64(mem.ullTotalPhys), + AvailableMemory: uint64(mem.ullAvailPhys), } } func GetRamInformation() (MemoryInformation, error) { - return getWinRamInfo(getWindowsTools().ProcGlobalMemoryStatusEx), nil + return getWinRamInfo(windows_system.GetWindowsTools().ProcGlobalMemoryStatusEx), nil } diff --git a/internal/processor/cpu.go b/internal/processor/cpu.go new file mode 100644 index 0000000..93e2034 --- /dev/null +++ b/internal/processor/cpu.go @@ -0,0 +1,34 @@ +package processor + +import "time" + +type ProcessorInformation struct { + IdleTimeSinceBoot uint64 + KernelTimeSinceBoot uint64 + UserTimeSinceBoot uint64 +} + +func (procInfo ProcessorInformation) Usage() ( + TotalCPUUsage float64, + KernelCPUUsage float64, + UserCPUUsage float64, + IdleCPUUsage float64, +) { + first := GetCPUInformation() + time.Sleep(time.Second) + second := GetCPUInformation() + + deltaIdleTime := second.IdleTimeSinceBoot - first.IdleTimeSinceBoot + deltaKernelTime := second.KernelTimeSinceBoot - first.KernelTimeSinceBoot + deltaUserTime := second.UserTimeSinceBoot - first.UserTimeSinceBoot + deltaTotalTime := deltaIdleTime + deltaKernelTime + deltaUserTime + + if deltaTotalTime > 0 { + TotalCPUUsage = ((float64(deltaKernelTime) + float64(deltaUserTime)) / float64(deltaTotalTime)) * 100 + UserCPUUsage = (float64(deltaUserTime) / float64(deltaTotalTime)) * 100 + KernelCPUUsage = (float64(deltaKernelTime) / float64(deltaTotalTime)) * 100 + IdleCPUUsage = (float64(deltaIdleTime) / float64(deltaTotalTime)) * 100 + } + + return TotalCPUUsage, KernelCPUUsage, UserCPUUsage, IdleCPUUsage +} diff --git a/internal/processor/linux_cpu.go b/internal/processor/linux_cpu.go new file mode 100644 index 0000000..4995b0e --- /dev/null +++ b/internal/processor/linux_cpu.go @@ -0,0 +1,50 @@ +//go:build linux + +package processor + +import ( + "bufio" + "fmt" + "log" + "os" + "regexp" + "strings" +) + +var numberRegex *regexp.Regexp = regexp.MustCompile(`\d+`) + +func getLinuxCpuInfo() (ProcessorInformation, error) { + procFile, err := os.Open("/proc/meminfo") + if err != nil { + return ProcessorInformation{}, fmt.Errorf("Could not find /proc/meminfo. Are you sure you are on linux?") + } + defer procFile.Close() + + fileReader := bufio.NewScanner(procFile) + + //var idleTime uint64 + //var kernelTime uint64 + //var userTime uint64 + + for fileReader.Scan() { + line := fileReader.Text() + + if strings.HasPrefix(line, "cpu ") { + fields := strings.Split(line, " ") + for _, field := range fields { + fmt.Print(field) + } + } + } + + return ProcessorInformation{}, nil +} + +func GetCPUInformation() ProcessorInformation { + info, err := getLinuxCpuInfo() + if err != nil { + log.Printf("Could not read processor info. Error: %v", err) + return ProcessorInformation{} + } + return info +} diff --git a/internal/processor/win_cpu.go b/internal/processor/win_cpu.go new file mode 100644 index 0000000..9e0ac86 --- /dev/null +++ b/internal/processor/win_cpu.go @@ -0,0 +1,48 @@ +//go:build windows + +package processor + +import ( + "rpi5-health/internal/windows_system" + "unsafe" + + "golang.org/x/sys/windows" +) + +type DWORD uint32 + +type PFILETIME struct { + dwLowDateTime DWORD + dwHighDateTime DWORD +} + +func getWinCpuInfo(getSystemTimes *windows.LazyProc) ProcessorInformation { + var lpIdleTime PFILETIME + var lpKernelTime PFILETIME + var lpUserTime PFILETIME + + getSystemTimes.Call( + uintptr(unsafe.Pointer(&lpIdleTime)), + uintptr(unsafe.Pointer(&lpKernelTime)), + uintptr(unsafe.Pointer(&lpUserTime)), + ) + + timeSpentIdlingTicks := fileTimeToTicks(lpIdleTime) + timeSpentByKernelTicks := fileTimeToTicks(lpKernelTime) - timeSpentIdlingTicks // kernel time includes idling by default + timeSpentByUserTicks := fileTimeToTicks(lpUserTime) + + return ProcessorInformation{ + IdleTimeSinceBoot: timeSpentIdlingTicks, + KernelTimeSinceBoot: timeSpentByKernelTicks, + UserTimeSinceBoot: timeSpentByUserTicks, + } +} + +func fileTimeToTicks(filetime PFILETIME) uint64 { + // 100 nanosecond intervals since 1601, january 1 + return (uint64(filetime.dwHighDateTime) << 32) | uint64(filetime.dwLowDateTime) +} + +func GetCPUInformation() ProcessorInformation { + return getWinCpuInfo(windows_system.GetWindowsTools().SystemTimes) +} diff --git a/internal/system_check/win.go b/internal/windows_system/win.go similarity index 67% rename from internal/system_check/win.go rename to internal/windows_system/win.go index b6e69bc..590fa4b 100644 --- a/internal/system_check/win.go +++ b/internal/windows_system/win.go @@ -1,6 +1,6 @@ //go:build windows -package system_check +package windows_system import ( "sync" @@ -17,14 +17,20 @@ type WindowsSystemCheckTools struct { * Windows memory information */ ProcGlobalMemoryStatusEx *windows.LazyProc + /* + * Windows processor information + */ + SystemTimes *windows.LazyProc } -func getWindowsTools() WindowsSystemCheckTools { +func GetWindowsTools() WindowsSystemCheckTools { kernel32 := sync.OnceValue(func() *windows.LazyDLL { return windows.NewLazySystemDLL("kernel32.dll") }) procGlobalMemoryStatusEx := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GlobalMemoryStatusEx") }) + systemTimes := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GetSystemTimes") }) return WindowsSystemCheckTools{ Kernel32: kernel32(), ProcGlobalMemoryStatusEx: procGlobalMemoryStatusEx(), + SystemTimes: systemTimes(), } }