added load average calculations and moved the testing from main to their own cli commands
This commit is contained in:
1 parent
8c931aa3d8
commit
b1d4f3565d
7 files changed
+271
-26
No files matched your search
@@ -1,35 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"rpi5-health/internal/cli"
|
"rpi5-health/internal/cli"
|
||||||
"rpi5-health/internal/http_internal"
|
"rpi5-health/internal/http_internal"
|
||||||
"rpi5-health/internal/memory"
|
|
||||||
"rpi5-health/internal/processor"
|
|
||||||
"rpi5-health/internal/uptime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
srv := http_internal.NewAppServer()
|
srv := http_internal.NewAppServer()
|
||||||
go srv.Start()
|
go srv.Start()
|
||||||
|
|
||||||
memInfo, _ := memory.GetRamInformation()
|
|
||||||
|
|
||||||
const GB = 1024 * 1024 * 1024
|
|
||||||
|
|
||||||
log.Printf("Total memory: %.2fGB\n", float64(memInfo.SystemMemory)/GB)
|
|
||||||
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()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
uptime, _ := uptime.GetUptimeInformation()
|
|
||||||
|
|
||||||
log.Printf("Total uptime: %s", uptime.FormatSeconds())
|
|
||||||
|
|
||||||
cli.Listen()
|
cli.Listen()
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
loadaverage "rpi5-health/internal/load_average"
|
||||||
|
"rpi5-health/internal/memory"
|
||||||
|
"rpi5-health/internal/processor"
|
||||||
|
"rpi5-health/internal/uptime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +34,28 @@ func Listen() {
|
|||||||
switch text {
|
switch text {
|
||||||
case "exit":
|
case "exit":
|
||||||
return
|
return
|
||||||
|
case "meminfo":
|
||||||
|
memInfo, _ := memory.GetRamInformation()
|
||||||
|
|
||||||
|
const GB = 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
fmt.Printf("Total memory: %.2fGB\n", float64(memInfo.SystemMemory)/GB)
|
||||||
|
fmt.Printf("Available memory: %.2fGB\n", float64(memInfo.AvailableMemory)/GB)
|
||||||
|
fmt.Printf("Used memory: %.2fGB\n", float64(memInfo.GetUsedMemory())/GB)
|
||||||
|
case "cpuinfo":
|
||||||
|
_, kernel, usage, idle := processor.GetCPUInformation().Usage()
|
||||||
|
|
||||||
|
fmt.Printf("Total time spent idling: %.2f%%\n", idle)
|
||||||
|
fmt.Printf("Total time spent in kernel mode: %.2f%%\n", kernel)
|
||||||
|
fmt.Printf("Total time spent in user mode: %.2f%%\n", usage)
|
||||||
|
case "uptime":
|
||||||
|
uptime, _ := uptime.GetUptimeInformation()
|
||||||
|
|
||||||
|
fmt.Printf("Total uptime: %s\n", uptime.FormatSeconds())
|
||||||
|
case "loadavg":
|
||||||
|
loadAvg, _ := loadaverage.GetLoadAverage()
|
||||||
|
|
||||||
|
fmt.Printf("Load average: L1: %.2f | L5: %.2f | L15: %.2f\n", loadAvg.Load1, loadAvg.Load5, loadAvg.Load15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,55 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package loadaverage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLinuxLoadAverage() (LoadAverageInformation, error) {
|
||||||
|
loadFile, err := os.Open("/proc/loadavg")
|
||||||
|
if err != nil {
|
||||||
|
return LoadAverageInformation{}, fmt.Errorf("Could not find /proc/loadavg. Are you sure you are on linux?")
|
||||||
|
}
|
||||||
|
defer loadFile.Close()
|
||||||
|
|
||||||
|
fileReader := bufio.NewScanner(loadFile)
|
||||||
|
|
||||||
|
var one_m float64
|
||||||
|
var five_m float64
|
||||||
|
var fifteen_m float64
|
||||||
|
|
||||||
|
fileReader.Scan()
|
||||||
|
if fileReader.Err() != nil {
|
||||||
|
return LoadAverageInformation{}, fmt.Errorf("Could not read /proc/uptime.")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := fileReader.Text()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fields[0] One minute load average
|
||||||
|
* fields[1] Five minute load average
|
||||||
|
* fields[2] Fifteen minute load average
|
||||||
|
* fields[3] Currently executing/total scheduled
|
||||||
|
* fields[4] Most recent process PID
|
||||||
|
*/
|
||||||
|
fields := strings.Split(data, " ")
|
||||||
|
|
||||||
|
one_m, _ = strconv.ParseFloat(fields[0], 64)
|
||||||
|
five_m, _ = strconv.ParseFloat(fields[1], 64)
|
||||||
|
fifteen_m, _ = strconv.ParseFloat(fields[2], 64)
|
||||||
|
|
||||||
|
return LoadAverageInformation{
|
||||||
|
Load1: one_m,
|
||||||
|
Load5: five_m,
|
||||||
|
Load15: fifteen_m,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLoadAverage() (LoadAverageInformation, error) {
|
||||||
|
return getLinuxLoadAverage()
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package loadaverage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants for calculating EMA
|
||||||
|
|
||||||
|
const SAMPLE_DELAY time.Duration = time.Second * 5
|
||||||
|
|
||||||
|
var (
|
||||||
|
ONE_MIN_ALPHA float64 = math.Exp(-SAMPLE_DELAY.Seconds() / 60.0) // e^(-dt/W), where dt is the sample time and W
|
||||||
|
FIVE_MIN_ALPHA float64 = math.Exp(-SAMPLE_DELAY.Seconds() / 300.0) // is the target timeframe. eg.: 60 for one min
|
||||||
|
FIFTEEN_MIN_ALPHA float64 = math.Exp(-SAMPLE_DELAY.Seconds() / 900.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoadAverageInformation struct {
|
||||||
|
/*
|
||||||
|
* Load average in the past minute
|
||||||
|
*/
|
||||||
|
Load1 float64
|
||||||
|
/*
|
||||||
|
* Load average in the past 5 minutes
|
||||||
|
*/
|
||||||
|
Load5 float64
|
||||||
|
/*
|
||||||
|
* Load average in the past 15 minutes
|
||||||
|
*/
|
||||||
|
Load15 float64
|
||||||
|
|
||||||
|
initialized bool // track whether this is the first measurement or not
|
||||||
|
}
|
||||||
|
|
||||||
|
func (loadAvg *LoadAverageInformation) Update(currentSample float64) {
|
||||||
|
if !loadAvg.initialized {
|
||||||
|
// Seed the initial averages with the first sample to avoid a slow ramp-up from zero
|
||||||
|
loadAvg.Load1 = currentSample
|
||||||
|
loadAvg.Load5 = currentSample
|
||||||
|
loadAvg.Load15 = currentSample
|
||||||
|
loadAvg.initialized = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAvg.Load1 = loadAvg.Load1*ONE_MIN_ALPHA + currentSample*(1.0-ONE_MIN_ALPHA)
|
||||||
|
loadAvg.Load5 = loadAvg.Load5*FIVE_MIN_ALPHA + currentSample*(1.0-FIVE_MIN_ALPHA)
|
||||||
|
loadAvg.Load15 = loadAvg.Load15*FIFTEEN_MIN_ALPHA + currentSample*(1.0-FIFTEEN_MIN_ALPHA)
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package loadaverage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"rpi5-health/internal/windows_system"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Formatting flag for PdhGetFormattedCounterValue to return a float64
|
||||||
|
PDH_FMT_DOUBLE = 0x00000200
|
||||||
|
ERROR_SUCCESS = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// PDH_FMT_COUNTERVALUE_DOUBLE maps the C struct _PDH_FMT_COUNTERVALUE for double values.
|
||||||
|
// * Note the explicit padding: C++ unions align to 8 bytes on 64-bit systems.
|
||||||
|
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
|
||||||
|
CStatus uint32
|
||||||
|
Pad uint32 // ! Crucial padding for 64-bit alignment before the float64
|
||||||
|
DoubleValue float64 // Represents the union where the double is stored
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
GlobalLoadAverage LoadAverageInformation
|
||||||
|
|
||||||
|
// 1. A RWMutex protects the struct from concurrent reads/writes
|
||||||
|
loadMu sync.RWMutex
|
||||||
|
|
||||||
|
startWorker = sync.OnceFunc(func() {
|
||||||
|
go loadAverageWorker()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadAverageWorker() {
|
||||||
|
var hQuery syscall.Handle // PDH_HQUERY
|
||||||
|
var hCounter syscall.Handle // PDH_HCOUNTER
|
||||||
|
|
||||||
|
wintools := windows_system.GetWindowsTools()
|
||||||
|
|
||||||
|
// initialize query
|
||||||
|
ret, _, _ := wintools.ProcPdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&hQuery)))
|
||||||
|
if ret != ERROR_SUCCESS {
|
||||||
|
fmt.Printf("PdhOpenQueryW failed: %x\n", ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer wintools.ProcPdhCloseQuery.Call(uintptr(hQuery))
|
||||||
|
|
||||||
|
counterPath, _ := syscall.UTF16PtrFromString(`\Processor(_Total)\% Processor Time`)
|
||||||
|
ret, _, _ = wintools.ProcPdhAddCounter.Call(
|
||||||
|
uintptr(hQuery),
|
||||||
|
uintptr(unsafe.Pointer(counterPath)),
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&hCounter)),
|
||||||
|
)
|
||||||
|
if ret != ERROR_SUCCESS {
|
||||||
|
fmt.Printf("PdhAddCounterW failed: %x\n", ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect initial data, "priming the counter"
|
||||||
|
wintools.ProcPdhCollectQueryData.Call(uintptr(hQuery))
|
||||||
|
|
||||||
|
// Infinite collection loop
|
||||||
|
for {
|
||||||
|
time.Sleep(SAMPLE_DELAY)
|
||||||
|
|
||||||
|
wintools.ProcPdhCollectQueryData.Call(uintptr(hQuery))
|
||||||
|
|
||||||
|
var counterType uint32
|
||||||
|
var value PDH_FMT_COUNTERVALUE_DOUBLE
|
||||||
|
|
||||||
|
ret, _, _ = wintools.ProcPdhGetFormattedCounterValue.Call(
|
||||||
|
uintptr(hCounter),
|
||||||
|
uintptr(PDH_FMT_DOUBLE),
|
||||||
|
uintptr(unsafe.Pointer(&counterType)),
|
||||||
|
uintptr(unsafe.Pointer(&value)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if ret == ERROR_SUCCESS {
|
||||||
|
loadMu.Lock()
|
||||||
|
GlobalLoadAverage.Update(value.DoubleValue)
|
||||||
|
loadMu.Unlock()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("PdhGetFormattedCounterValue failed: %x\n", ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLoadAverage() (LoadAverageInformation, error) {
|
||||||
|
startWorker()
|
||||||
|
loadMu.RLock()
|
||||||
|
defer loadMu.RUnlock()
|
||||||
|
return GlobalLoadAverage, nil
|
||||||
|
}
|
||||||
@@ -13,6 +13,10 @@ type WindowsSystemCheckTools struct {
|
|||||||
* Win32 API
|
* Win32 API
|
||||||
*/
|
*/
|
||||||
Kernel32 *windows.LazyDLL
|
Kernel32 *windows.LazyDLL
|
||||||
|
/*
|
||||||
|
* Performance Data Helper
|
||||||
|
*/
|
||||||
|
PDH *windows.LazyDLL
|
||||||
/*
|
/*
|
||||||
* Windows memory information
|
* Windows memory information
|
||||||
*/
|
*/
|
||||||
@@ -25,18 +29,53 @@ type WindowsSystemCheckTools struct {
|
|||||||
* Windows uptime metrics
|
* Windows uptime metrics
|
||||||
*/
|
*/
|
||||||
Uptime *windows.LazyProc
|
Uptime *windows.LazyProc
|
||||||
|
/*
|
||||||
|
* Opening PDH queries
|
||||||
|
*/
|
||||||
|
ProcPdhOpenQuery *windows.LazyProc
|
||||||
|
/*
|
||||||
|
* Adding counters to open queries
|
||||||
|
*/
|
||||||
|
ProcPdhAddCounter *windows.LazyProc
|
||||||
|
/*
|
||||||
|
* Collecting query data
|
||||||
|
*/
|
||||||
|
ProcPdhCollectQueryData *windows.LazyProc
|
||||||
|
/*
|
||||||
|
* Extracting formatted query value from data
|
||||||
|
*/
|
||||||
|
ProcPdhGetFormattedCounterValue *windows.LazyProc
|
||||||
|
/*
|
||||||
|
* Closing PDH queries
|
||||||
|
*/
|
||||||
|
ProcPdhCloseQuery *windows.LazyProc
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWindowsTools() WindowsSystemCheckTools {
|
func GetWindowsTools() WindowsSystemCheckTools {
|
||||||
kernel32 := sync.OnceValue(func() *windows.LazyDLL { return windows.NewLazySystemDLL("kernel32.dll") })
|
kernel32 := sync.OnceValue(func() *windows.LazyDLL { return windows.NewLazySystemDLL("kernel32.dll") })
|
||||||
|
pdh := sync.OnceValue(func() *windows.LazyDLL { return windows.NewLazySystemDLL("pdh.dll") })
|
||||||
|
|
||||||
procGlobalMemoryStatusEx := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GlobalMemoryStatusEx") })
|
procGlobalMemoryStatusEx := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GlobalMemoryStatusEx") })
|
||||||
systemTimes := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GetSystemTimes") })
|
systemTimes := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GetSystemTimes") })
|
||||||
uptime := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GetTickCount64") })
|
uptime := sync.OnceValue(func() *windows.LazyProc { return kernel32().NewProc("GetTickCount64") })
|
||||||
|
|
||||||
|
// PDH functions
|
||||||
|
procPdhOpenQuery := sync.OnceValue(func() *windows.LazyProc { return pdh().NewProc("PdhOpenQueryW") })
|
||||||
|
procPdhAddCounter := sync.OnceValue(func() *windows.LazyProc { return pdh().NewProc("PdhAddEnglishCounterW") })
|
||||||
|
procPdhCollectQueryData := sync.OnceValue(func() *windows.LazyProc { return pdh().NewProc("PdhCollectQueryData") })
|
||||||
|
procPdhGetFormattedCounterValue := sync.OnceValue(func() *windows.LazyProc { return pdh().NewProc("PdhGetFormattedCounterValue") })
|
||||||
|
procPdhCloseQuery := sync.OnceValue(func() *windows.LazyProc { return pdh().NewProc("PdhCloseQuery") })
|
||||||
|
|
||||||
return WindowsSystemCheckTools{
|
return WindowsSystemCheckTools{
|
||||||
Kernel32: kernel32(),
|
Kernel32: kernel32(),
|
||||||
ProcGlobalMemoryStatusEx: procGlobalMemoryStatusEx(),
|
PDH: pdh(),
|
||||||
SystemTimes: systemTimes(),
|
ProcGlobalMemoryStatusEx: procGlobalMemoryStatusEx(),
|
||||||
Uptime: uptime(),
|
SystemTimes: systemTimes(),
|
||||||
|
Uptime: uptime(),
|
||||||
|
ProcPdhOpenQuery: procPdhOpenQuery(),
|
||||||
|
ProcPdhAddCounter: procPdhAddCounter(),
|
||||||
|
ProcPdhCollectQueryData: procPdhCollectQueryData(),
|
||||||
|
ProcPdhGetFormattedCounterValue: procPdhGetFormattedCounterValue(),
|
||||||
|
ProcPdhCloseQuery: procPdhCloseQuery(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user