mirror of
https://github.com/datarhei/core.git
synced 2025-10-06 00:17:07 +08:00
227 lines
4.5 KiB
Go
227 lines
4.5 KiB
Go
package psutil
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
|
|
psprocess "github.com/shirou/gopsutil/v3/process"
|
|
)
|
|
|
|
type Process interface {
|
|
// CPUPercent returns the current CPU load for this process only. The values
|
|
// are normed to the range of 0 to 100.
|
|
CPUPercent() (*CPUInfoStat, error)
|
|
|
|
// VirtualMemory returns the current memory usage in bytes of this process only.
|
|
VirtualMemory() (uint64, error)
|
|
|
|
// Stop will stop collecting CPU and memory data for this process.
|
|
Stop()
|
|
}
|
|
|
|
type process struct {
|
|
pid int32
|
|
hasCgroup bool
|
|
cpuLimit uint64
|
|
ncpu float64
|
|
proc *psprocess.Process
|
|
|
|
stopTicker context.CancelFunc
|
|
|
|
lock sync.RWMutex
|
|
statCurrent cpuTimesStat
|
|
statCurrentTime time.Time
|
|
statPrevious cpuTimesStat
|
|
statPreviousTime time.Time
|
|
|
|
imposeLimit bool
|
|
}
|
|
|
|
func (u *util) Process(pid int32, limit bool) (Process, error) {
|
|
p := &process{
|
|
pid: pid,
|
|
hasCgroup: u.hasCgroup,
|
|
cpuLimit: u.cpuLimit,
|
|
ncpu: u.ncpu,
|
|
imposeLimit: limit,
|
|
}
|
|
|
|
proc, err := psprocess.NewProcess(pid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.proc = proc
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
p.stopTicker = cancel
|
|
go p.tick(ctx, 1000*time.Millisecond)
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func NewProcess(pid int32, limit bool) (Process, error) {
|
|
return DefaultUtil.Process(pid, limit)
|
|
}
|
|
|
|
func (p *process) tick(ctx context.Context, interval time.Duration) {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
if p.imposeLimit {
|
|
go p.limit(ctx, interval)
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case t := <-ticker.C:
|
|
stat := p.collect()
|
|
|
|
p.lock.Lock()
|
|
p.statPrevious, p.statCurrent = p.statCurrent, stat
|
|
p.statPreviousTime, p.statCurrentTime = p.statCurrentTime, t
|
|
p.lock.Unlock()
|
|
|
|
pct, _ := p.CPUPercent()
|
|
pcpu := (pct.System + pct.User + pct.Other) / 100
|
|
|
|
fmt.Printf("%d\t%0.2f%%\n", p.pid, pcpu*100*p.ncpu)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *process) limit(ctx context.Context, interval time.Duration) {
|
|
var limit float64 = 50.0 / 100.0 / p.ncpu
|
|
var workingrate float64 = -1
|
|
|
|
counter := 0
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
pct, _ := p.CPUPercent()
|
|
/*
|
|
pct.System *= p.ncpu
|
|
pct.Idle *= p.ncpu
|
|
pct.User *= p.ncpu
|
|
pct.Other *= p.ncpu
|
|
*/
|
|
|
|
pcpu := (pct.System + pct.User + pct.Other) / 100
|
|
|
|
if workingrate < 0 {
|
|
workingrate = limit
|
|
} else {
|
|
workingrate = math.Min(workingrate/pcpu*limit, 1)
|
|
}
|
|
|
|
worktime := float64(interval.Nanoseconds()) * workingrate
|
|
sleeptime := float64(interval.Nanoseconds()) - worktime
|
|
/*
|
|
if counter%20 == 0 {
|
|
fmt.Printf("\nPID\t%%CPU\twork quantum\tsleep quantum\tactive rate\n")
|
|
counter = 0
|
|
}
|
|
|
|
fmt.Printf("%d\t%0.2f%%\t%.2f us\t%.2f us\t%0.2f%%\n", p.pid, pcpu*100*p.ncpu, worktime/1000, sleeptime/1000, workingrate*100)
|
|
*/
|
|
if p.imposeLimit {
|
|
p.proc.Resume()
|
|
}
|
|
time.Sleep(time.Duration(worktime) * time.Nanosecond)
|
|
|
|
if sleeptime > 0 {
|
|
if p.imposeLimit {
|
|
p.proc.Suspend()
|
|
}
|
|
time.Sleep(time.Duration(sleeptime) * time.Nanosecond)
|
|
}
|
|
|
|
counter++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *process) collect() cpuTimesStat {
|
|
stat, err := p.cpuTimes()
|
|
if err != nil {
|
|
return cpuTimesStat{
|
|
total: float64(time.Now().Unix()),
|
|
idle: float64(time.Now().Unix()),
|
|
}
|
|
}
|
|
|
|
return *stat
|
|
}
|
|
|
|
func (p *process) Stop() {
|
|
p.stopTicker()
|
|
}
|
|
|
|
func (p *process) cpuTimes() (*cpuTimesStat, error) {
|
|
times, err := p.proc.Times()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &cpuTimesStat{
|
|
total: cpuTotal(times),
|
|
system: times.System,
|
|
user: times.User,
|
|
}
|
|
|
|
s.other = s.total - s.system - s.user
|
|
if s.other < 0.0001 {
|
|
s.other = 0
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (p *process) CPUPercent() (*CPUInfoStat, error) {
|
|
var diff float64
|
|
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.hasCgroup && p.cpuLimit > 0 {
|
|
diff = float64(p.cpuLimit) * (p.statCurrentTime.Sub(p.statPreviousTime)).Seconds() / 1e9
|
|
} else {
|
|
diff = p.statCurrentTime.Sub(p.statPreviousTime).Seconds() * p.ncpu
|
|
}
|
|
|
|
s := &CPUInfoStat{
|
|
System: 0,
|
|
User: 0,
|
|
Idle: 0,
|
|
Other: 0,
|
|
}
|
|
|
|
if diff <= 0 {
|
|
return s, nil
|
|
}
|
|
|
|
s.System = 100 * (p.statCurrent.system - p.statPrevious.system) / diff
|
|
s.User = 100 * (p.statCurrent.user - p.statPrevious.user) / diff
|
|
s.Idle = 100 * (p.statCurrent.idle - p.statPrevious.idle) / diff
|
|
s.Other = 100 * (p.statCurrent.other - p.statPrevious.other) / diff
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (p *process) VirtualMemory() (uint64, error) {
|
|
info, err := p.proc.MemoryInfo()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return info.RSS, nil
|
|
}
|