cpu_windows.go 9.2 KB

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