//go:build windows // +build windows package cpu import ( "context" "fmt" "unsafe" "github.com/shirou/gopsutil/v3/internal/common" "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) var ( procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount") procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") ) type win32_Processor struct { 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 } 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 } // 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 }