diff --git a/cpu/cpu.go b/cpu/cpu.go index ae6ab04d646ee9a59165cf870069b6c5aa81cdff..6f76b764c089f872dc98638fd193d631b1d9aa78 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -5,6 +5,7 @@ import ( "runtime" "strconv" "strings" + "sync" "github.com/shirou/gopsutil/internal/common" ) @@ -39,15 +40,23 @@ type InfoStat struct { Flags []string `json:"flags"` } +type lastPercent struct { + sync.Mutex + lastCPUTimes []TimesStat + lastPerCPUTimes []TimesStat +} + +var lastCPUPercent lastPercent var invoke common.Invoker func init() { invoke = common.Invoke{} + lastCPUPercent.Lock() + lastCPUPercent.lastCPUTimes, _ = Times(false) + lastCPUPercent.lastPerCPUTimes, _ = Times(true) + lastCPUPercent.Unlock() } -var lastCPUTimes []TimesStat -var lastPerCPUTimes []TimesStat - func Counts(logical bool) (int, error) { return runtime.NumCPU(), nil } diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index c3d56ebe371576dbbf828660a35984cf5577d01b..6082ffcc5f117f16c5a6fcbdf7c1f796772104c8 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -94,6 +94,40 @@ func testCPUPercent(t *testing.T, percpu bool) { } } +func testCPUPercentLastUsed(t *testing.T, percpu bool) { + + numcpu := runtime.NumCPU() + testCount := 10 + + if runtime.GOOS != "windows" { + testCount = 2 + v, err := Percent(time.Millisecond, percpu) + if err != nil { + t.Errorf("error %v", err) + } + // Skip CircleCI which CPU num is different + if os.Getenv("CIRCLECI") != "true" { + if (percpu && len(v) != numcpu) || (!percpu && len(v) != 1) { + t.Fatalf("wrong number of entries from CPUPercent: %v", v) + } + } + } + for i := 0; i < testCount; i++ { + v, err := Percent(0, percpu) + if err != nil { + t.Errorf("error %v", err) + } + time.Sleep(1 * time.Millisecond) + for _, percent := range v { + // Check for slightly greater then 100% to account for any rounding issues. + if percent < 0.0 || percent > 100.0001*float64(numcpu) { + t.Fatalf("CPUPercent value is invalid: %f", percent) + } + } + } + +} + func TestCPUPercent(t *testing.T) { testCPUPercent(t, false) } @@ -101,3 +135,11 @@ func TestCPUPercent(t *testing.T) { func TestCPUPercentPerCpu(t *testing.T) { testCPUPercent(t, true) } + +func TestCPUPercentIntervalZero(t *testing.T) { + testCPUPercentLastUsed(t, false) +} + +func TestCPUPercentIntervalZeroPerCPU(t *testing.T) { + testCPUPercentLastUsed(t, true) +} diff --git a/cpu/cpu_unix.go b/cpu/cpu_unix.go index 9f1ea4d772aaac4cea7a6025e60f5cc8b3f3d6c1..0ed7d62ad8dfd2d7dc12ac85476fd7f99f98266e 100644 --- a/cpu/cpu_unix.go +++ b/cpu/cpu_unix.go @@ -7,24 +7,46 @@ import ( "time" ) -func Percent(interval time.Duration, percpu bool) ([]float64, error) { - getAllBusy := func(t TimesStat) (float64, float64) { - busy := t.User + t.System + t.Nice + t.Iowait + t.Irq + - t.Softirq + t.Steal + t.Guest + t.GuestNice + t.Stolen - return busy + t.Idle, busy +func getAllBusy(t TimesStat) (float64, float64) { + busy := t.User + t.System + t.Nice + t.Iowait + t.Irq + + t.Softirq + t.Steal + t.Guest + t.GuestNice + t.Stolen + return busy + t.Idle, busy +} + +func calculateBusy(t1, t2 TimesStat) float64 { + t1All, t1Busy := getAllBusy(t1) + t2All, t2Busy := getAllBusy(t2) + + if t2Busy <= t1Busy { + return 0 + } + if t2All <= t1All { + return 1 + } + return (t2Busy - t1Busy) / (t2All - t1All) * 100 +} + +func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) { + // Make sure the CPU measurements have the same length. + if len(t1) != len(t2) { + return nil, fmt.Errorf( + "received two CPU counts: %d != %d", + len(t1), len(t2), + ) } - calculate := func(t1, t2 TimesStat) float64 { - t1All, t1Busy := getAllBusy(t1) - t2All, t2Busy := getAllBusy(t2) + ret := make([]float64, len(t1)) + for i, t := range t2 { + ret[i] = calculateBusy(t1[i], t) + } + return ret, nil +} - if t2Busy <= t1Busy { - return 0 - } - if t2All <= t1All { - return 1 - } - return (t2Busy - t1Busy) / (t2All - t1All) * 100 +//Percent calculates the percentage of cpu used either per CPU or combined. +//If an interval of 0 is given it will compare the current cpu times against the last call. +func Percent(interval time.Duration, percpu bool) ([]float64, error) { + if interval <= 0 { + return percentUsedFromLastCall(percpu) } // Get CPU usage at the start of the interval. @@ -33,9 +55,7 @@ func Percent(interval time.Duration, percpu bool) ([]float64, error) { return nil, err } - if interval > 0 { - time.Sleep(interval) - } + time.Sleep(interval) // And at the end of the interval. cpuTimes2, err := Times(percpu) @@ -43,17 +63,28 @@ func Percent(interval time.Duration, percpu bool) ([]float64, error) { return nil, err } - // Make sure the CPU measurements have the same length. - if len(cpuTimes1) != len(cpuTimes2) { - return nil, fmt.Errorf( - "received two CPU counts: %d != %d", - len(cpuTimes1), len(cpuTimes2), - ) + return calculateAllBusy(cpuTimes1, cpuTimes2) +} + +func percentUsedFromLastCall(percpu bool) ([]float64, error) { + cpuTimes, err := Times(percpu) + if err != nil { + return nil, err + } + lastCPUPercent.Lock() + defer lastCPUPercent.Unlock() + var lastTimes []TimesStat + if percpu { + lastTimes = lastCPUPercent.lastPerCPUTimes + lastCPUPercent.lastPerCPUTimes = cpuTimes + } else { + lastTimes = lastCPUPercent.lastCPUTimes + lastCPUPercent.lastCPUTimes = cpuTimes } - ret := make([]float64, len(cpuTimes1)) - for i, t := range cpuTimes2 { - ret[i] = calculate(cpuTimes1[i], t) + if lastTimes == nil { + return nil, fmt.Errorf("Error getting times for cpu percent. LastTimes was nil") } - return ret, nil + return calculateAllBusy(lastTimes, cpuTimes) + }