9a38e4f766
https://github.com/shirou/gopsutil/pull/895 is merged and fixes our problem. Time to update. Since there is no new version just yet, updating to the sha.
256 lines
8.8 KiB
Go
256 lines
8.8 KiB
Go
// +build windows
|
|
|
|
package cpu
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"github.com/StackExchange/wmi"
|
|
"github.com/shirou/gopsutil/internal/common"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount")
|
|
procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
|
|
)
|
|
|
|
type Win32_Processor struct {
|
|
LoadPercentage *uint16
|
|
Family uint16
|
|
Manufacturer string
|
|
Name string
|
|
NumberOfLogicalProcessors uint32
|
|
NumberOfCores uint32
|
|
ProcessorID *string
|
|
Stepping *string
|
|
MaxClockSpeed uint32
|
|
}
|
|
|
|
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
|
|
// defined in windows api doc with the following
|
|
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
|
|
// additional fields documented here
|
|
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
|
|
type win32_SystemProcessorPerformanceInformation struct {
|
|
IdleTime int64 // idle time in 100ns (this is not a filetime).
|
|
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
|
|
UserTime int64 // usertime in 100ns (this is not a filetime).
|
|
DpcTime int64 // dpc time in 100ns (this is not a filetime).
|
|
InterruptTime int64 // interrupt time in 100ns
|
|
InterruptCount uint32
|
|
}
|
|
|
|
// Win32_PerfFormattedData_PerfOS_System struct to have count of processes and processor queue length
|
|
type Win32_PerfFormattedData_PerfOS_System struct {
|
|
Processes uint32
|
|
ProcessorQueueLength uint32
|
|
}
|
|
|
|
const (
|
|
ClocksPerSec = 10000000.0
|
|
|
|
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
|
|
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
|
|
win32_SystemProcessorPerformanceInformationClass = 8
|
|
|
|
// size of systemProcessorPerformanceInfoSize in memory
|
|
win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{}))
|
|
)
|
|
|
|
// Times returns times stat per cpu and combined for all CPUs
|
|
func Times(percpu bool) ([]TimesStat, error) {
|
|
return TimesWithContext(context.Background(), percpu)
|
|
}
|
|
|
|
func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
|
|
if percpu {
|
|
return perCPUTimes()
|
|
}
|
|
|
|
var ret []TimesStat
|
|
var lpIdleTime common.FILETIME
|
|
var lpKernelTime common.FILETIME
|
|
var lpUserTime common.FILETIME
|
|
r, _, _ := common.ProcGetSystemTimes.Call(
|
|
uintptr(unsafe.Pointer(&lpIdleTime)),
|
|
uintptr(unsafe.Pointer(&lpKernelTime)),
|
|
uintptr(unsafe.Pointer(&lpUserTime)))
|
|
if r == 0 {
|
|
return ret, windows.GetLastError()
|
|
}
|
|
|
|
LOT := float64(0.0000001)
|
|
HIT := (LOT * 4294967296.0)
|
|
idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime)))
|
|
user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime)))
|
|
kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime)))
|
|
system := (kernel - idle)
|
|
|
|
ret = append(ret, TimesStat{
|
|
CPU: "cpu-total",
|
|
Idle: float64(idle),
|
|
User: float64(user),
|
|
System: float64(system),
|
|
})
|
|
return ret, nil
|
|
}
|
|
|
|
func Info() ([]InfoStat, error) {
|
|
return InfoWithContext(context.Background())
|
|
}
|
|
|
|
func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
|
|
var ret []InfoStat
|
|
var dst []Win32_Processor
|
|
q := wmi.CreateQuery(&dst, "")
|
|
if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil {
|
|
return ret, err
|
|
}
|
|
|
|
var procID string
|
|
for i, l := range dst {
|
|
procID = ""
|
|
if l.ProcessorID != nil {
|
|
procID = *l.ProcessorID
|
|
}
|
|
|
|
cpu := InfoStat{
|
|
CPU: int32(i),
|
|
Family: fmt.Sprintf("%d", l.Family),
|
|
VendorID: l.Manufacturer,
|
|
ModelName: l.Name,
|
|
Cores: int32(l.NumberOfLogicalProcessors),
|
|
PhysicalID: procID,
|
|
Mhz: float64(l.MaxClockSpeed),
|
|
Flags: []string{},
|
|
}
|
|
ret = append(ret, cpu)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// ProcInfo returns processes count and processor queue length in the system.
|
|
// There is a single queue for processor even on multiprocessors systems.
|
|
func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) {
|
|
return ProcInfoWithContext(context.Background())
|
|
}
|
|
|
|
func ProcInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_PerfOS_System, error) {
|
|
var ret []Win32_PerfFormattedData_PerfOS_System
|
|
q := wmi.CreateQuery(&ret, "")
|
|
err := common.WMIQueryWithContext(ctx, q, &ret)
|
|
if err != nil {
|
|
return []Win32_PerfFormattedData_PerfOS_System{}, err
|
|
}
|
|
return ret, err
|
|
}
|
|
|
|
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
|
|
func perCPUTimes() ([]TimesStat, error) {
|
|
var ret []TimesStat
|
|
stats, err := perfInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for core, v := range stats {
|
|
c := TimesStat{
|
|
CPU: fmt.Sprintf("cpu%d", core),
|
|
User: float64(v.UserTime) / ClocksPerSec,
|
|
System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec,
|
|
Idle: float64(v.IdleTime) / ClocksPerSec,
|
|
Irq: float64(v.InterruptTime) / ClocksPerSec,
|
|
}
|
|
ret = append(ret, c)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// makes call to Windows API function to retrieve performance information for each core
|
|
func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) {
|
|
// Make maxResults large for safety.
|
|
// We can't invoke the api call with a results array that's too small.
|
|
// If we have more than 2056 cores on a single host, then it's probably the future.
|
|
maxBuffer := 2056
|
|
// buffer for results from the windows proc
|
|
resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer)
|
|
// size of the buffer in memory
|
|
bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer)
|
|
// size of the returned response
|
|
var retSize uint32
|
|
|
|
// Invoke windows api proc.
|
|
// The returned err from the windows dll proc will always be non-nil even when successful.
|
|
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
|
|
retCode, _, err := common.ProcNtQuerySystemInformation.Call(
|
|
win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation
|
|
uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer
|
|
bufferSize, // size of the buffer in memory
|
|
uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this
|
|
)
|
|
|
|
// check return code for errors
|
|
if retCode != 0 {
|
|
return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error())
|
|
}
|
|
|
|
// calculate the number of returned elements based on the returned size
|
|
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
|
|
|
|
// trim results to the number of returned elements
|
|
resultBuffer = resultBuffer[:numReturnedElements]
|
|
|
|
return resultBuffer, nil
|
|
}
|
|
|
|
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
|
|
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
|
|
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
|
|
type systemInfo struct {
|
|
wProcessorArchitecture uint16
|
|
wReserved uint16
|
|
dwPageSize uint32
|
|
lpMinimumApplicationAddress uintptr
|
|
lpMaximumApplicationAddress uintptr
|
|
dwActiveProcessorMask uintptr
|
|
dwNumberOfProcessors uint32
|
|
dwProcessorType uint32
|
|
dwAllocationGranularity uint32
|
|
wProcessorLevel uint16
|
|
wProcessorRevision uint16
|
|
}
|
|
|
|
func CountsWithContext(ctx context.Context, logical bool) (int, error) {
|
|
if logical {
|
|
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
|
|
err := procGetActiveProcessorCount.Find()
|
|
if err == nil { // Win7+
|
|
ret, _, _ := procGetActiveProcessorCount.Call(uintptr(0xffff)) // ALL_PROCESSOR_GROUPS is 0xffff according to Rust's winapi lib https://docs.rs/winapi/*/x86_64-pc-windows-msvc/src/winapi/shared/ntdef.rs.html#120
|
|
if ret != 0 {
|
|
return int(ret), nil
|
|
}
|
|
}
|
|
var systemInfo systemInfo
|
|
_, _, err = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
|
|
if systemInfo.dwNumberOfProcessors == 0 {
|
|
return 0, err
|
|
}
|
|
return int(systemInfo.dwNumberOfProcessors), nil
|
|
}
|
|
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
|
|
// for the time being, try with unreliable and slow WMI call…
|
|
var dst []Win32_Processor
|
|
q := wmi.CreateQuery(&dst, "")
|
|
if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil {
|
|
return 0, err
|
|
}
|
|
var count uint32
|
|
for _, d := range dst {
|
|
count += d.NumberOfCores
|
|
}
|
|
return int(count), nil
|
|
}
|