1// +build windows 2 3package cpu 4 5import ( 6 "context" 7 "fmt" 8 "unsafe" 9 10 "github.com/StackExchange/wmi" 11 "github.com/shirou/gopsutil/internal/common" 12 "golang.org/x/sys/windows" 13) 14 15var ( 16 procGetActiveProcessorCount = common.Modkernel32.NewProc("GetActiveProcessorCount") 17 procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") 18) 19 20type Win32_Processor struct { 21 LoadPercentage *uint16 22 Family uint16 23 Manufacturer string 24 Name string 25 NumberOfLogicalProcessors uint32 26 NumberOfCores uint32 27 ProcessorID *string 28 Stepping *string 29 MaxClockSpeed uint32 30} 31 32// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION 33// defined in windows api doc with the following 34// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information 35// additional fields documented here 36// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm 37type win32_SystemProcessorPerformanceInformation struct { 38 IdleTime int64 // idle time in 100ns (this is not a filetime). 39 KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime). 40 UserTime int64 // usertime in 100ns (this is not a filetime). 41 DpcTime int64 // dpc time in 100ns (this is not a filetime). 42 InterruptTime int64 // interrupt time in 100ns 43 InterruptCount uint32 44} 45 46// Win32_PerfFormattedData_PerfOS_System struct to have count of processes and processor queue length 47type Win32_PerfFormattedData_PerfOS_System struct { 48 Processes uint32 49 ProcessorQueueLength uint32 50} 51 52const ( 53 ClocksPerSec = 10000000.0 54 55 // systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation 56 // https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0 57 win32_SystemProcessorPerformanceInformationClass = 8 58 59 // size of systemProcessorPerformanceInfoSize in memory 60 win32_SystemProcessorPerformanceInfoSize = uint32(unsafe.Sizeof(win32_SystemProcessorPerformanceInformation{})) 61) 62 63// Times returns times stat per cpu and combined for all CPUs 64func Times(percpu bool) ([]TimesStat, error) { 65 return TimesWithContext(context.Background(), percpu) 66} 67 68func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) { 69 if percpu { 70 return perCPUTimes() 71 } 72 73 var ret []TimesStat 74 var lpIdleTime common.FILETIME 75 var lpKernelTime common.FILETIME 76 var lpUserTime common.FILETIME 77 r, _, _ := common.ProcGetSystemTimes.Call( 78 uintptr(unsafe.Pointer(&lpIdleTime)), 79 uintptr(unsafe.Pointer(&lpKernelTime)), 80 uintptr(unsafe.Pointer(&lpUserTime))) 81 if r == 0 { 82 return ret, windows.GetLastError() 83 } 84 85 LOT := float64(0.0000001) 86 HIT := (LOT * 4294967296.0) 87 idle := ((HIT * float64(lpIdleTime.DwHighDateTime)) + (LOT * float64(lpIdleTime.DwLowDateTime))) 88 user := ((HIT * float64(lpUserTime.DwHighDateTime)) + (LOT * float64(lpUserTime.DwLowDateTime))) 89 kernel := ((HIT * float64(lpKernelTime.DwHighDateTime)) + (LOT * float64(lpKernelTime.DwLowDateTime))) 90 system := (kernel - idle) 91 92 ret = append(ret, TimesStat{ 93 CPU: "cpu-total", 94 Idle: float64(idle), 95 User: float64(user), 96 System: float64(system), 97 }) 98 return ret, nil 99} 100 101func Info() ([]InfoStat, error) { 102 return InfoWithContext(context.Background()) 103} 104 105func InfoWithContext(ctx context.Context) ([]InfoStat, error) { 106 var ret []InfoStat 107 var dst []Win32_Processor 108 q := wmi.CreateQuery(&dst, "") 109 if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil { 110 return ret, err 111 } 112 113 var procID string 114 for i, l := range dst { 115 procID = "" 116 if l.ProcessorID != nil { 117 procID = *l.ProcessorID 118 } 119 120 cpu := InfoStat{ 121 CPU: int32(i), 122 Family: fmt.Sprintf("%d", l.Family), 123 VendorID: l.Manufacturer, 124 ModelName: l.Name, 125 Cores: int32(l.NumberOfLogicalProcessors), 126 PhysicalID: procID, 127 Mhz: float64(l.MaxClockSpeed), 128 Flags: []string{}, 129 } 130 ret = append(ret, cpu) 131 } 132 133 return ret, nil 134} 135 136// ProcInfo returns processes count and processor queue length in the system. 137// There is a single queue for processor even on multiprocessors systems. 138func ProcInfo() ([]Win32_PerfFormattedData_PerfOS_System, error) { 139 return ProcInfoWithContext(context.Background()) 140} 141 142func ProcInfoWithContext(ctx context.Context) ([]Win32_PerfFormattedData_PerfOS_System, error) { 143 var ret []Win32_PerfFormattedData_PerfOS_System 144 q := wmi.CreateQuery(&ret, "") 145 err := common.WMIQueryWithContext(ctx, q, &ret) 146 if err != nil { 147 return []Win32_PerfFormattedData_PerfOS_System{}, err 148 } 149 return ret, err 150} 151 152// perCPUTimes returns times stat per cpu, per core and overall for all CPUs 153func perCPUTimes() ([]TimesStat, error) { 154 var ret []TimesStat 155 stats, err := perfInfo() 156 if err != nil { 157 return nil, err 158 } 159 for core, v := range stats { 160 c := TimesStat{ 161 CPU: fmt.Sprintf("cpu%d", core), 162 User: float64(v.UserTime) / ClocksPerSec, 163 System: float64(v.KernelTime-v.IdleTime) / ClocksPerSec, 164 Idle: float64(v.IdleTime) / ClocksPerSec, 165 Irq: float64(v.InterruptTime) / ClocksPerSec, 166 } 167 ret = append(ret, c) 168 } 169 return ret, nil 170} 171 172// makes call to Windows API function to retrieve performance information for each core 173func perfInfo() ([]win32_SystemProcessorPerformanceInformation, error) { 174 // Make maxResults large for safety. 175 // We can't invoke the api call with a results array that's too small. 176 // If we have more than 2056 cores on a single host, then it's probably the future. 177 maxBuffer := 2056 178 // buffer for results from the windows proc 179 resultBuffer := make([]win32_SystemProcessorPerformanceInformation, maxBuffer) 180 // size of the buffer in memory 181 bufferSize := uintptr(win32_SystemProcessorPerformanceInfoSize) * uintptr(maxBuffer) 182 // size of the returned response 183 var retSize uint32 184 185 // Invoke windows api proc. 186 // The returned err from the windows dll proc will always be non-nil even when successful. 187 // See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information 188 retCode, _, err := common.ProcNtQuerySystemInformation.Call( 189 win32_SystemProcessorPerformanceInformationClass, // System Information Class -> SystemProcessorPerformanceInformation 190 uintptr(unsafe.Pointer(&resultBuffer[0])), // pointer to first element in result buffer 191 bufferSize, // size of the buffer in memory 192 uintptr(unsafe.Pointer(&retSize)), // pointer to the size of the returned results the windows proc will set this 193 ) 194 195 // check return code for errors 196 if retCode != 0 { 197 return nil, fmt.Errorf("call to NtQuerySystemInformation returned %d. err: %s", retCode, err.Error()) 198 } 199 200 // calculate the number of returned elements based on the returned size 201 numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize 202 203 // trim results to the number of returned elements 204 resultBuffer = resultBuffer[:numReturnedElements] 205 206 return resultBuffer, nil 207} 208 209// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API. 210// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396 211// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43 212type systemInfo struct { 213 wProcessorArchitecture uint16 214 wReserved uint16 215 dwPageSize uint32 216 lpMinimumApplicationAddress uintptr 217 lpMaximumApplicationAddress uintptr 218 dwActiveProcessorMask uintptr 219 dwNumberOfProcessors uint32 220 dwProcessorType uint32 221 dwAllocationGranularity uint32 222 wProcessorLevel uint16 223 wProcessorRevision uint16 224} 225 226func CountsWithContext(ctx context.Context, logical bool) (int, error) { 227 if logical { 228 // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97 229 err := procGetActiveProcessorCount.Find() 230 if err == nil { // Win7+ 231 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 232 if ret != 0 { 233 return int(ret), nil 234 } 235 } 236 var systemInfo systemInfo 237 _, _, err = procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo))) 238 if systemInfo.dwNumberOfProcessors == 0 { 239 return 0, err 240 } 241 return int(systemInfo.dwNumberOfProcessors), nil 242 } 243 // physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499 244 // for the time being, try with unreliable and slow WMI call… 245 var dst []Win32_Processor 246 q := wmi.CreateQuery(&dst, "") 247 if err := common.WMIQueryWithContext(ctx, q, &dst); err != nil { 248 return 0, err 249 } 250 var count uint32 251 for _, d := range dst { 252 count += d.NumberOfCores 253 } 254 return int(count), nil 255} 256