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