| package agent
|
|
|
| import (
|
| "math"
|
| "runtime"
|
|
|
| "github.com/henrygd/beszel/internal/entities/system"
|
| "github.com/shirou/gopsutil/v4/cpu"
|
| )
|
|
|
| var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
| var lastPerCoreCpuTimes = make(map[uint16][]cpu.TimesStat)
|
|
|
|
|
|
|
| func init() {
|
| if times, err := cpu.Times(false); err == nil && len(times) > 0 {
|
| lastCpuTimes[60000] = times[0]
|
| }
|
| if perCoreTimes, err := cpu.Times(true); err == nil && len(perCoreTimes) > 0 {
|
| lastPerCoreCpuTimes[60000] = perCoreTimes
|
| }
|
| }
|
|
|
|
|
| type CpuMetrics struct {
|
| Total float64
|
| User float64
|
| System float64
|
| Iowait float64
|
| Steal float64
|
| Idle float64
|
| }
|
|
|
|
|
|
|
| func getCpuMetrics(cacheTimeMs uint16) (CpuMetrics, error) {
|
| times, err := cpu.Times(false)
|
| if err != nil || len(times) == 0 {
|
| return CpuMetrics{}, err
|
| }
|
|
|
| if _, ok := lastCpuTimes[cacheTimeMs]; !ok {
|
| lastCpuTimes[cacheTimeMs] = lastCpuTimes[60000]
|
| }
|
|
|
| t1 := lastCpuTimes[cacheTimeMs]
|
| t2 := times[0]
|
|
|
| t1All, _ := getAllBusy(t1)
|
| t2All, _ := getAllBusy(t2)
|
|
|
| totalDelta := t2All - t1All
|
| if totalDelta <= 0 {
|
| return CpuMetrics{}, nil
|
| }
|
|
|
| metrics := CpuMetrics{
|
| Total: calculateBusy(t1, t2),
|
| User: clampPercent((t2.User - t1.User) / totalDelta * 100),
|
| System: clampPercent((t2.System - t1.System) / totalDelta * 100),
|
| Iowait: clampPercent((t2.Iowait - t1.Iowait) / totalDelta * 100),
|
| Steal: clampPercent((t2.Steal - t1.Steal) / totalDelta * 100),
|
| Idle: clampPercent((t2.Idle - t1.Idle) / totalDelta * 100),
|
| }
|
|
|
| lastCpuTimes[cacheTimeMs] = times[0]
|
| return metrics, nil
|
| }
|
|
|
|
|
| func clampPercent(value float64) float64 {
|
| return math.Min(100, math.Max(0, value))
|
| }
|
|
|
|
|
|
|
| func getPerCoreCpuUsage(cacheTimeMs uint16) (system.Uint8Slice, error) {
|
| perCoreTimes, err := cpu.Times(true)
|
| if err != nil || len(perCoreTimes) == 0 {
|
| return nil, err
|
| }
|
|
|
|
|
| if _, ok := lastPerCoreCpuTimes[cacheTimeMs]; !ok {
|
| lastPerCoreCpuTimes[cacheTimeMs] = lastPerCoreCpuTimes[60000]
|
| }
|
|
|
| lastTimes := lastPerCoreCpuTimes[cacheTimeMs]
|
|
|
|
|
| length := min(len(lastTimes), len(perCoreTimes))
|
|
|
| usage := make([]uint8, length)
|
| for i := 0; i < length; i++ {
|
| t1 := lastTimes[i]
|
| t2 := perCoreTimes[i]
|
| usage[i] = uint8(math.Round(calculateBusy(t1, t2)))
|
| }
|
|
|
| lastPerCoreCpuTimes[cacheTimeMs] = perCoreTimes
|
| return usage, nil
|
| }
|
|
|
|
|
|
|
|
|
| func calculateBusy(t1, t2 cpu.TimesStat) float64 {
|
| t1All, t1Busy := getAllBusy(t1)
|
| t2All, t2Busy := getAllBusy(t2)
|
|
|
| if t2All <= t1All || t2Busy <= t1Busy {
|
| return 0
|
| }
|
| return clampPercent((t2Busy - t1Busy) / (t2All - t1All) * 100)
|
| }
|
|
|
|
|
|
|
|
|
| func getAllBusy(t cpu.TimesStat) (float64, float64) {
|
| tot := t.Total()
|
| if runtime.GOOS == "linux" {
|
| tot -= t.Guest
|
| tot -= t.GuestNice
|
| }
|
|
|
| busy := tot - t.Idle - t.Iowait
|
|
|
| return tot, busy
|
| }
|
|
|