Skip to content

Commit 1e34da6

Browse files
authored
Merge pull request #2125 from srebhan/fix_cpu_windows_total
[cpu][windows]: compute total counters from individual stats to handle processor groups correctly
2 parents 61f8802 + 38a01b4 commit 1e34da6

1 file changed

Lines changed: 40 additions & 48 deletions

File tree

cpu/cpu_windows.go

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -104,39 +104,51 @@ func Times(percpu bool) ([]TimesStat, error) {
104104
}
105105

106106
func TimesWithContext(_ context.Context, percpu bool) ([]TimesStat, error) {
107+
// Get the CPU performance counters per processor via Windows API
108+
stats, err := perfInfo()
109+
if err != nil {
110+
return nil, err
111+
}
112+
107113
if percpu {
108-
return perCPUTimes()
114+
ret := make([]TimesStat, 0, len(stats))
115+
for core, v := range stats {
116+
c := TimesStat{
117+
CPU: fmt.Sprintf("cpu%d", core),
118+
User: float64(v.UserTime) / ClocksPerSec,
119+
System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec,
120+
Idle: float64(v.IdleTime) / ClocksPerSec,
121+
Irq: float64(v.InterruptTime) / ClocksPerSec,
122+
}
123+
ret = append(ret, c)
124+
}
125+
return ret, nil
109126
}
110127

111-
var ret []TimesStat
112-
var lpIdleTime common.FILETIME
113-
var lpKernelTime common.FILETIME
114-
var lpUserTime common.FILETIME
115-
// GetSystemTimes returns 0 for error, in which case we check err,
116-
// see https://pkg.go.dev/golang.org/x/sys/windows#LazyProc.Call
117-
r, _, err := common.ProcGetSystemTimes.Call(
118-
uintptr(unsafe.Pointer(&lpIdleTime)),
119-
uintptr(unsafe.Pointer(&lpKernelTime)),
120-
uintptr(unsafe.Pointer(&lpUserTime)))
121-
if r == 0 {
122-
return nil, err
128+
// Accumulate the times over all CPUs as GetSystemTimes() will only return
129+
// the counters for the current processor group. This causes issues for
130+
// machines with more than 64 logical processors when the current thread is
131+
// switched to another processor group as then the counters are not
132+
// monotonic anymore.
133+
var total win32_SystemProcessorPerformanceInformation
134+
for _, v := range stats {
135+
total.IdleTime += v.IdleTime
136+
total.KernelTime += v.KernelTime
137+
total.UserTime += v.UserTime
138+
total.DpcTime += v.DpcTime
139+
total.InterruptTime += v.InterruptTime
140+
total.InterruptCount += v.InterruptCount
123141
}
124142

125-
// Do all arithmetic on the integer tick counts and convert to float64
126-
// only once, mirroring perCPUTimes. Converting each FILETIME half to
127-
// float64 first loses precision on large counters and can make the
128-
// returned values non-monotonic. See issue #2110.
129-
idle := uint64(lpIdleTime.DwHighDateTime)<<32 | uint64(lpIdleTime.DwLowDateTime)
130-
user := uint64(lpUserTime.DwHighDateTime)<<32 | uint64(lpUserTime.DwLowDateTime)
131-
kernel := uint64(lpKernelTime.DwHighDateTime)<<32 | uint64(lpKernelTime.DwLowDateTime)
132-
133-
ret = append(ret, TimesStat{
134-
CPU: "cpu-total",
135-
Idle: float64(idle) / ClocksPerSec,
136-
User: float64(user) / ClocksPerSec,
137-
System: float64(kernel-idle) / ClocksPerSec, // kernel time includes idle time
138-
})
139-
return ret, nil
143+
return []TimesStat{
144+
{
145+
CPU: "cpu-total",
146+
User: float64(total.UserTime) / ClocksPerSec,
147+
System: float64(total.KernelTime-total.IdleTime) / ClocksPerSec,
148+
Idle: float64(total.IdleTime) / ClocksPerSec,
149+
Irq: float64(total.InterruptTime) / ClocksPerSec,
150+
},
151+
}, nil
140152
}
141153

142154
func Info() ([]InfoStat, error) {
@@ -243,26 +255,6 @@ func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
243255
return ret, nil
244256
}
245257

246-
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
247-
func perCPUTimes() ([]TimesStat, error) {
248-
var ret []TimesStat
249-
stats, err := perfInfo()
250-
if err != nil {
251-
return nil, err
252-
}
253-
for core, v := range stats {
254-
c := TimesStat{
255-
CPU: fmt.Sprintf("cpu%d", core),
256-
User: float64(v.UserTime) / ClocksPerSec,
257-
System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec,
258-
Idle: float64(v.IdleTime) / ClocksPerSec,
259-
Irq: float64(v.InterruptTime) / ClocksPerSec,
260-
}
261-
ret = append(ret, c)
262-
}
263-
return ret, nil
264-
}
265-
266258
// makes call to Windows API function to retrieve performance information for each core
267259
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
268260
// On hosts with more than 64 logical CPUs Windows splits CPUs into Processor Groups

0 commit comments

Comments
 (0)