Note, dependency has to be pinned to a specific release version commit hashes since it doesn't follow Go module v2+ guidelines. see: https://github.com/shirou/gopsutil/issues/663 https://github.com/golang/go/issues/34402
		
			
				
	
	
		
			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 (
 | 
						|
	win32_TicksPerSecond = 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) / win32_TicksPerSecond,
 | 
						|
			System: float64(v.KernelTime-v.IdleTime) / win32_TicksPerSecond,
 | 
						|
			Idle:   float64(v.IdleTime) / win32_TicksPerSecond,
 | 
						|
			Irq:    float64(v.InterruptTime) / win32_TicksPerSecond,
 | 
						|
		}
 | 
						|
		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
 | 
						|
}
 |