mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
collect top process
This commit is contained in:
119
agent/cpu.go
119
agent/cpu.go
@@ -2,14 +2,19 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v4/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
||||||
var lastPerCoreCpuTimes = 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
|
// init initializes the CPU monitoring by storing the initial CPU times
|
||||||
// for the default 60-second cache interval.
|
// for the default 60-second cache interval.
|
||||||
@@ -20,6 +25,16 @@ func init() {
|
|||||||
if perCoreTimes, err := cpu.Times(true); err == nil {
|
if perCoreTimes, err := cpu.Times(true); err == nil {
|
||||||
lastPerCoreCpuTimes[60000] = perCoreTimes
|
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
|
// CpuMetrics contains detailed CPU usage breakdown
|
||||||
@@ -105,6 +120,110 @@ func getPerCoreCpuUsage(cacheTimeMs uint16) (system.Uint8Slice, error) {
|
|||||||
return usage, nil
|
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.
|
// 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,
|
// It computes the ratio of busy time to total time elapsed between t1 and t2,
|
||||||
// returning a percentage clamped between 0 and 100.
|
// 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)
|
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
|
// per-core cpu usage
|
||||||
if perCoreUsage, err := getPerCoreCpuUsage(cacheTimeMs); err == nil {
|
if perCoreUsage, err := getPerCoreCpuUsage(cacheTimeMs); err == nil {
|
||||||
systemStats.CpuCoresUsage = perCoreUsage
|
systemStats.CpuCoresUsage = perCoreUsage
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ type Stats struct {
|
|||||||
MaxDiskIO [2]uint64 `json:"diom,omitzero" cbor:"-"` // [max read bytes, max write bytes]
|
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]
|
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..]
|
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.
|
// 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"`
|
Info Info `json:"info" cbor:"1,keyasint"`
|
||||||
Containers []*container.Stats `json:"container" cbor:"2,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