mirror of
https://github.com/henrygd/beszel.git
synced 2026-01-17 15:50:21 +00:00
Compare commits
1 Commits
v0.18.0
...
top-proces
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbbdd49fc2 |
119
agent/cpu.go
119
agent/cpu.go
@@ -2,14 +2,19 @@ package agent
|
||||
|
||||
import (
|
||||
"math"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/system"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
)
|
||||
|
||||
var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
||||
var lastPerCoreCpuTimes = make(map[uint16][]cpu.TimesStat)
|
||||
var lastProcessCpuTimes = make(map[uint16]map[int32]float64)
|
||||
var lastProcessCpuSampleTime = make(map[uint16]time.Time)
|
||||
|
||||
// init initializes the CPU monitoring by storing the initial CPU times
|
||||
// for the default 60-second cache interval.
|
||||
@@ -20,6 +25,16 @@ func init() {
|
||||
if perCoreTimes, err := cpu.Times(true); err == nil {
|
||||
lastPerCoreCpuTimes[60000] = perCoreTimes
|
||||
}
|
||||
if processes, err := process.Processes(); err == nil {
|
||||
snapshot := make(map[int32]float64, len(processes))
|
||||
for _, proc := range processes {
|
||||
if times, err := proc.Times(); err == nil {
|
||||
snapshot[proc.Pid] = times.Total()
|
||||
}
|
||||
}
|
||||
lastProcessCpuTimes[60000] = snapshot
|
||||
lastProcessCpuSampleTime[60000] = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// CpuMetrics contains detailed CPU usage breakdown
|
||||
@@ -105,6 +120,110 @@ func getPerCoreCpuUsage(cacheTimeMs uint16) (system.Uint8Slice, error) {
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// getTopCpuProcess returns the process with the highest CPU usage since the last run
|
||||
// for the given cache interval. It returns nil if insufficient data is available.
|
||||
func getTopCpuProcess(cacheTimeMs uint16) (*system.TopCpuProcess, error) {
|
||||
processes, err := process.Processes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
lastTimes, ok := lastProcessCpuTimes[cacheTimeMs]
|
||||
if !ok {
|
||||
if fallback := lastProcessCpuTimes[60000]; fallback != nil {
|
||||
copied := make(map[int32]float64, len(fallback))
|
||||
for pid, total := range fallback {
|
||||
copied[pid] = total
|
||||
}
|
||||
lastTimes = copied
|
||||
lastProcessCpuTimes[cacheTimeMs] = copied
|
||||
} else {
|
||||
lastTimes = make(map[int32]float64)
|
||||
lastProcessCpuTimes[cacheTimeMs] = lastTimes
|
||||
}
|
||||
}
|
||||
|
||||
lastSample := lastProcessCpuSampleTime[cacheTimeMs]
|
||||
if lastSample.IsZero() {
|
||||
if fallback := lastProcessCpuSampleTime[60000]; !fallback.IsZero() {
|
||||
lastSample = fallback
|
||||
lastProcessCpuSampleTime[cacheTimeMs] = fallback
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := now.Sub(lastSample).Seconds()
|
||||
if lastSample.IsZero() || elapsed <= 0 {
|
||||
snapshot := make(map[int32]float64, len(processes))
|
||||
for _, proc := range processes {
|
||||
if times, err := proc.Times(); err == nil {
|
||||
snapshot[proc.Pid] = times.Total()
|
||||
}
|
||||
}
|
||||
lastProcessCpuTimes[cacheTimeMs] = snapshot
|
||||
lastProcessCpuSampleTime[cacheTimeMs] = now
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cpuCount := float64(runtime.NumCPU())
|
||||
if cpuCount <= 0 {
|
||||
cpuCount = 1
|
||||
}
|
||||
|
||||
snapshot := make(map[int32]float64, len(processes))
|
||||
var topName string
|
||||
var topPercent float64
|
||||
|
||||
for _, proc := range processes {
|
||||
times, err := proc.Times()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
total := times.Total()
|
||||
pid := proc.Pid
|
||||
snapshot[pid] = total
|
||||
|
||||
lastTotal, ok := lastTimes[pid]
|
||||
if !ok || total <= lastTotal {
|
||||
continue
|
||||
}
|
||||
|
||||
percent := clampPercent((total - lastTotal) / (elapsed * cpuCount) * 100)
|
||||
if percent <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
name, err := proc.Name()
|
||||
if err != nil || name == "" {
|
||||
if exe, exeErr := proc.Exe(); exeErr == nil && exe != "" {
|
||||
name = filepath.Base(exe)
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if percent > topPercent {
|
||||
topPercent = percent
|
||||
topName = name
|
||||
}
|
||||
}
|
||||
|
||||
lastProcessCpuTimes[cacheTimeMs] = snapshot
|
||||
lastProcessCpuSampleTime[cacheTimeMs] = now
|
||||
|
||||
if topName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &system.TopCpuProcess{
|
||||
Name: topName,
|
||||
Percent: topPercent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// calculateBusy calculates the CPU busy percentage between two time points.
|
||||
// It computes the ratio of busy time to total time elapsed between t1 and t2,
|
||||
// returning a percentage clamped between 0 and 100.
|
||||
|
||||
@@ -98,6 +98,15 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
|
||||
slog.Error("Error getting cpu metrics", "err", err)
|
||||
}
|
||||
|
||||
if topProcess, err := getTopCpuProcess(cacheTimeMs); err == nil {
|
||||
if topProcess != nil {
|
||||
topProcess.Percent = twoDecimals(topProcess.Percent)
|
||||
systemStats.TopCpuProcess = topProcess
|
||||
}
|
||||
} else {
|
||||
slog.Error("Error getting top cpu process", "err", err)
|
||||
}
|
||||
|
||||
// per-core cpu usage
|
||||
if perCoreUsage, err := getPerCoreCpuUsage(cacheTimeMs); err == nil {
|
||||
systemStats.CpuCoresUsage = perCoreUsage
|
||||
|
||||
@@ -47,6 +47,7 @@ type Stats struct {
|
||||
MaxDiskIO [2]uint64 `json:"diom,omitzero" cbor:"-"` // [max read bytes, max write bytes]
|
||||
CpuBreakdown []float64 `json:"cpub,omitempty" cbor:"33,keyasint,omitempty"` // [user, system, iowait, steal, idle]
|
||||
CpuCoresUsage Uint8Slice `json:"cpus,omitempty" cbor:"34,keyasint,omitempty"` // per-core busy usage [CPU0..]
|
||||
TopCpuProcess *TopCpuProcess `json:"tcp,omitempty" cbor:"35,keyasint,omitempty"`
|
||||
}
|
||||
|
||||
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
|
||||
@@ -153,3 +154,8 @@ type CombinedData struct {
|
||||
Info Info `json:"info" cbor:"1,keyasint"`
|
||||
Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
|
||||
}
|
||||
|
||||
type TopCpuProcess struct {
|
||||
Name string `json:"n" cbor:"0,keyasint"`
|
||||
Percent float64 `json:"p" cbor:"1,keyasint"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user