// 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 }