cpu_windows.go 8.0 KB

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