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