// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package hoststats
import (
"math"
"github.com/shirou/gopsutil/v3/cpu"
)
// cpuStatsCalculator calculates cpu usage percentages
type cpuStatsCalculator struct {
prev cpu . TimesStat
prevBusy float64
prevTotal float64
}
// calculate the current cpu usage percentages.
// Since the cpu.TimesStat captures the total time a cpu spent in various states
// this function tracks the last seen stat and derives each cpu state's utilization
// as a percentage of the total change in cpu time between calls.
// The first time calculate is called CPUStats will report %100 idle
// usage since there is not a previous value to calculate against
func ( h * cpuStatsCalculator ) calculate ( times cpu . TimesStat ) * CPUStats {
// sum all none idle counters to get the total busy cpu time
currentBusy := times . User + times . System + times . Nice + times . Iowait + times . Irq +
times . Softirq + times . Steal + times . Guest + times . GuestNice
// sum of the total cpu time
currentTotal := currentBusy + times . Idle
// calculate how much cpu time has passed since last calculation
deltaTotal := currentTotal - h . prevTotal
stats := & CPUStats {
CPU : times . CPU ,
// calculate each percentage as the ratio of the change
// in each state's time to the total change in cpu time
Idle : ( ( times . Idle - h . prev . Idle ) / deltaTotal ) * 100 ,
User : ( ( times . User - h . prev . User ) / deltaTotal ) * 100 ,
System : ( ( times . System - h . prev . System ) / deltaTotal ) * 100 ,
Iowait : ( ( times . Iowait - h . prev . Iowait ) / deltaTotal ) * 100 ,
Total : ( ( currentBusy - h . prevBusy ) / deltaTotal ) * 100 ,
}
// Protect against any invalid values
if math . IsNaN ( stats . Idle ) || math . IsInf ( stats . Idle , 0 ) {
stats . Idle = 100.0
}
if math . IsNaN ( stats . User ) || math . IsInf ( stats . User , 0 ) {
stats . User = 0.0
}
if math . IsNaN ( stats . System ) || math . IsInf ( stats . System , 0 ) {
stats . System = 0.0
}
if math . IsNaN ( stats . Iowait ) || math . IsInf ( stats . Iowait , 0 ) {
stats . Iowait = 0.0
}
if math . IsNaN ( stats . Total ) || math . IsInf ( stats . Total , 0 ) {
stats . Total = 0.0
}
h . prev = times
h . prevTotal = currentTotal
h . prevBusy = currentBusy
return stats
}
func ( c * Collector ) collectCPUStats ( ) ( cpus [ ] * CPUStats , err error ) {
cpuStats , err := cpu . Times ( true )
if err != nil {
return nil , err
}
cs := make ( [ ] * CPUStats , len ( cpuStats ) )
for idx , cpuStat := range cpuStats {
percentCalculator , ok := c . cpuCalculator [ cpuStat . CPU ]
if ! ok {
percentCalculator = & cpuStatsCalculator { }
c . cpuCalculator [ cpuStat . CPU ] = percentCalculator
}
cs [ idx ] = percentCalculator . calculate ( cpuStat )
}
return cs , nil
}