mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-19 22:09:03 +08:00
* fix: Update CPU usage retrieval to include detailed percentage information * feat: Enhance CPU metrics by adding CPU frequency and detailed usage percentages * feat: Add CPU frequency metric to dashboard base information
319 lines
7.2 KiB
Go
319 lines
7.2 KiB
Go
package psutil
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/shirou/gopsutil/v4/cpu"
|
|
)
|
|
|
|
const (
|
|
resetInterval = 1 * time.Minute
|
|
fastInterval = 3 * time.Second
|
|
)
|
|
|
|
type CPUStat struct {
|
|
Idle uint64
|
|
Total uint64
|
|
}
|
|
|
|
type CPUDetailedStat struct {
|
|
User uint64
|
|
Nice uint64
|
|
System uint64
|
|
Idle uint64
|
|
Iowait uint64
|
|
Irq uint64
|
|
Softirq uint64
|
|
Steal uint64
|
|
Guest uint64
|
|
GuestNice uint64
|
|
Total uint64
|
|
}
|
|
|
|
type CPUDetailedPercent struct {
|
|
User float64 `json:"user"`
|
|
System float64 `json:"system"`
|
|
Nice float64 `json:"nice"`
|
|
Idle float64 `json:"idle"`
|
|
Iowait float64 `json:"iowait"`
|
|
Irq float64 `json:"irq"`
|
|
Softirq float64 `json:"softirq"`
|
|
Steal float64 `json:"steal"`
|
|
}
|
|
|
|
func (c *CPUDetailedPercent) GetCPUDetailedPercent() []float64 {
|
|
return []float64{c.User, c.System, c.Nice, c.Idle, c.Iowait, c.Irq, c.Softirq, c.Steal}
|
|
}
|
|
|
|
type CPUUsageState struct {
|
|
mu sync.Mutex
|
|
lastTotalStat *CPUStat
|
|
lastPerCPUStat []CPUStat
|
|
lastDetailStat *CPUDetailedStat
|
|
lastSampleTime time.Time
|
|
|
|
cachedTotalUsage float64
|
|
cachedPerCore []float64
|
|
cachedDetailedPercent CPUDetailedPercent
|
|
}
|
|
|
|
type CPUInfoState struct {
|
|
mu sync.RWMutex
|
|
initialized bool
|
|
cachedInfo []cpu.InfoStat
|
|
cachedPhysCores int
|
|
cachedLogicCores int
|
|
}
|
|
|
|
func (c *CPUUsageState) GetCPUUsage() (float64, []float64, []float64) {
|
|
c.mu.Lock()
|
|
|
|
now := time.Now()
|
|
|
|
if !c.lastSampleTime.IsZero() && now.Sub(c.lastSampleTime) < fastInterval {
|
|
result := c.cachedTotalUsage
|
|
perCore := c.cachedPerCore
|
|
detailed := c.cachedDetailedPercent
|
|
c.mu.Unlock()
|
|
return result, perCore, detailed.GetCPUDetailedPercent()
|
|
}
|
|
|
|
needReset := c.lastSampleTime.IsZero() || now.Sub(c.lastSampleTime) >= resetInterval
|
|
c.mu.Unlock()
|
|
|
|
if needReset {
|
|
firstTotal, firstDetail, firstPer := readAllCPUStat()
|
|
time.Sleep(100 * time.Millisecond)
|
|
secondTotal, secondDetail, secondPer := readAllCPUStat()
|
|
|
|
totalUsage := calcCPUPercent(firstTotal, secondTotal)
|
|
detailedPercent := calcCPUDetailedPercent(firstDetail, secondDetail)
|
|
|
|
perCore := make([]float64, len(secondPer))
|
|
for i := range secondPer {
|
|
perCore[i] = calcCPUPercent(firstPer[i], secondPer[i])
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.cachedTotalUsage = totalUsage
|
|
c.cachedPerCore = perCore
|
|
c.cachedDetailedPercent = detailedPercent
|
|
c.lastTotalStat = &secondTotal
|
|
c.lastDetailStat = &secondDetail
|
|
c.lastPerCPUStat = secondPer
|
|
c.lastSampleTime = time.Now()
|
|
c.mu.Unlock()
|
|
|
|
return totalUsage, perCore, detailedPercent.GetCPUDetailedPercent()
|
|
}
|
|
|
|
curTotal, curDetail, curPer := readAllCPUStat()
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
totalUsage := calcCPUPercent(*c.lastTotalStat, curTotal)
|
|
detailedPercent := calcCPUDetailedPercent(*c.lastDetailStat, curDetail)
|
|
|
|
if len(c.cachedPerCore) != len(curPer) {
|
|
c.cachedPerCore = make([]float64, len(curPer))
|
|
}
|
|
for i := range curPer {
|
|
c.cachedPerCore[i] = calcCPUPercent(c.lastPerCPUStat[i], curPer[i])
|
|
}
|
|
|
|
c.cachedTotalUsage = totalUsage
|
|
c.cachedPerCore = c.cachedPerCore
|
|
c.cachedDetailedPercent = detailedPercent
|
|
c.lastTotalStat = &curTotal
|
|
c.lastDetailStat = &curDetail
|
|
c.lastPerCPUStat = curPer
|
|
c.lastSampleTime = time.Now()
|
|
|
|
return totalUsage, c.cachedPerCore, detailedPercent.GetCPUDetailedPercent()
|
|
}
|
|
|
|
func (c *CPUUsageState) NumCPU() int {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
return len(c.cachedPerCore)
|
|
}
|
|
|
|
func (c *CPUInfoState) GetCPUInfo(forceRefresh bool) ([]cpu.InfoStat, error) {
|
|
c.mu.RLock()
|
|
if c.initialized && c.cachedInfo != nil && !forceRefresh {
|
|
defer c.mu.RUnlock()
|
|
return c.cachedInfo, nil
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
info, err := cpu.Info()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.cachedInfo = info
|
|
c.initialized = true
|
|
c.mu.Unlock()
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func (c *CPUInfoState) GetPhysicalCores(forceRefresh bool) (int, error) {
|
|
c.mu.RLock()
|
|
if c.initialized && c.cachedPhysCores > 0 && !forceRefresh {
|
|
defer c.mu.RUnlock()
|
|
return c.cachedPhysCores, nil
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
cores, err := cpu.Counts(false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.cachedPhysCores = cores
|
|
c.initialized = true
|
|
c.mu.Unlock()
|
|
|
|
return cores, nil
|
|
}
|
|
|
|
func (c *CPUInfoState) GetLogicalCores(forceRefresh bool) (int, error) {
|
|
c.mu.RLock()
|
|
if c.initialized && c.cachedLogicCores > 0 && !forceRefresh {
|
|
defer c.mu.RUnlock()
|
|
return c.cachedLogicCores, nil
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
cores, err := cpu.Counts(true)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.cachedLogicCores = cores
|
|
c.initialized = true
|
|
c.mu.Unlock()
|
|
|
|
return cores, nil
|
|
}
|
|
|
|
func readProcStat() ([]byte, error) {
|
|
return os.ReadFile("/proc/stat")
|
|
}
|
|
|
|
func parseCPUFields(line string) []uint64 {
|
|
fields := strings.Fields(line)
|
|
if len(fields) <= 1 {
|
|
return nil
|
|
}
|
|
fields = fields[1:]
|
|
|
|
nums := make([]uint64, len(fields))
|
|
for i, f := range fields {
|
|
v, _ := strconv.ParseUint(f, 10, 64)
|
|
nums[i] = v
|
|
}
|
|
return nums
|
|
}
|
|
|
|
func calcIdleAndTotal(nums []uint64) (idle, total uint64) {
|
|
if len(nums) < 5 {
|
|
return 0, 0
|
|
}
|
|
idle = nums[3] + nums[4]
|
|
for _, v := range nums {
|
|
total += v
|
|
}
|
|
return
|
|
}
|
|
|
|
func readAllCPUStat() (CPUStat, CPUDetailedStat, []CPUStat) {
|
|
data, err := readProcStat()
|
|
if err != nil {
|
|
return CPUStat{}, CPUDetailedStat{}, nil
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
if len(lines) == 0 {
|
|
return CPUStat{}, CPUDetailedStat{}, nil
|
|
}
|
|
|
|
firstLine := lines[0]
|
|
nums := parseCPUFields(firstLine)
|
|
|
|
idle, total := calcIdleAndTotal(nums)
|
|
cpuStat := CPUStat{Idle: idle, Total: total}
|
|
|
|
if len(nums) < 10 {
|
|
padded := make([]uint64, 10)
|
|
copy(padded, nums)
|
|
nums = padded
|
|
}
|
|
detailedStat := CPUDetailedStat{
|
|
User: nums[0],
|
|
Nice: nums[1],
|
|
System: nums[2],
|
|
Idle: nums[3],
|
|
Iowait: nums[4],
|
|
Irq: nums[5],
|
|
Softirq: nums[6],
|
|
Steal: nums[7],
|
|
Guest: nums[8],
|
|
GuestNice: nums[9],
|
|
}
|
|
detailedStat.Total = detailedStat.User + detailedStat.Nice + detailedStat.System +
|
|
detailedStat.Idle + detailedStat.Iowait + detailedStat.Irq + detailedStat.Softirq + detailedStat.Steal
|
|
|
|
var perCPUStats []CPUStat
|
|
for _, line := range lines[1:] {
|
|
if !strings.HasPrefix(line, "cpu") {
|
|
continue
|
|
}
|
|
if len(line) < 4 || line[3] < '0' || line[3] > '9' {
|
|
continue
|
|
}
|
|
|
|
perNums := parseCPUFields(line)
|
|
perIdle, perTotal := calcIdleAndTotal(perNums)
|
|
perCPUStats = append(perCPUStats, CPUStat{Idle: perIdle, Total: perTotal})
|
|
}
|
|
|
|
return cpuStat, detailedStat, perCPUStats
|
|
}
|
|
|
|
func calcCPUPercent(prev, cur CPUStat) float64 {
|
|
deltaIdle := float64(cur.Idle - prev.Idle)
|
|
deltaTotal := float64(cur.Total - prev.Total)
|
|
if deltaTotal <= 0 {
|
|
return 0
|
|
}
|
|
return (1 - deltaIdle/deltaTotal) * 100
|
|
}
|
|
|
|
func calcCPUDetailedPercent(prev, cur CPUDetailedStat) CPUDetailedPercent {
|
|
deltaTotal := float64(cur.Total - prev.Total)
|
|
if deltaTotal <= 0 {
|
|
return CPUDetailedPercent{Idle: 100}
|
|
}
|
|
|
|
return CPUDetailedPercent{
|
|
User: float64(cur.User-prev.User) / deltaTotal * 100,
|
|
System: float64(cur.System-prev.System) / deltaTotal * 100,
|
|
Nice: float64(cur.Nice-prev.Nice) / deltaTotal * 100,
|
|
Idle: float64(cur.Idle-prev.Idle) / deltaTotal * 100,
|
|
Iowait: float64(cur.Iowait-prev.Iowait) / deltaTotal * 100,
|
|
Irq: float64(cur.Irq-prev.Irq) / deltaTotal * 100,
|
|
Softirq: float64(cur.Softirq-prev.Softirq) / deltaTotal * 100,
|
|
Steal: float64(cur.Steal-prev.Steal) / deltaTotal * 100,
|
|
}
|
|
}
|