host_windows.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. // +build windows
  2. package host
  3. import (
  4. "context"
  5. "fmt"
  6. "math"
  7. "strconv"
  8. "strings"
  9. "sync/atomic"
  10. "syscall"
  11. "time"
  12. "unsafe"
  13. "github.com/shirou/gopsutil/internal/common"
  14. "github.com/shirou/gopsutil/process"
  15. "github.com/yusufpapurcu/wmi"
  16. "golang.org/x/sys/windows"
  17. )
  18. var (
  19. procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
  20. procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount")
  21. procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64")
  22. procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo")
  23. procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion")
  24. )
  25. // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
  26. type osVersionInfoExW struct {
  27. dwOSVersionInfoSize uint32
  28. dwMajorVersion uint32
  29. dwMinorVersion uint32
  30. dwBuildNumber uint32
  31. dwPlatformId uint32
  32. szCSDVersion [128]uint16
  33. wServicePackMajor uint16
  34. wServicePackMinor uint16
  35. wSuiteMask uint16
  36. wProductType uint8
  37. wReserved uint8
  38. }
  39. type systemInfo struct {
  40. wProcessorArchitecture uint16
  41. wReserved uint16
  42. dwPageSize uint32
  43. lpMinimumApplicationAddress uintptr
  44. lpMaximumApplicationAddress uintptr
  45. dwActiveProcessorMask uintptr
  46. dwNumberOfProcessors uint32
  47. dwProcessorType uint32
  48. dwAllocationGranularity uint32
  49. wProcessorLevel uint16
  50. wProcessorRevision uint16
  51. }
  52. type msAcpi_ThermalZoneTemperature struct {
  53. Active bool
  54. CriticalTripPoint uint32
  55. CurrentTemperature uint32
  56. InstanceName string
  57. }
  58. func HostIDWithContext(ctx context.Context) (string, error) {
  59. // there has been reports of issues on 32bit using golang.org/x/sys/windows/registry, see https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612
  60. // for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue
  61. var h windows.Handle
  62. err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h)
  63. if err != nil {
  64. return "", err
  65. }
  66. defer windows.RegCloseKey(h)
  67. const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16
  68. const uuidLen = 36
  69. var regBuf [windowsRegBufLen]uint16
  70. bufLen := uint32(windowsRegBufLen)
  71. var valType uint32
  72. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
  73. if err != nil {
  74. return "", err
  75. }
  76. hostID := windows.UTF16ToString(regBuf[:])
  77. hostIDLen := len(hostID)
  78. if hostIDLen != uuidLen {
  79. return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
  80. }
  81. return strings.ToLower(hostID), nil
  82. }
  83. func numProcs(ctx context.Context) (uint64, error) {
  84. procs, err := process.PidsWithContext(ctx)
  85. if err != nil {
  86. return 0, err
  87. }
  88. return uint64(len(procs)), nil
  89. }
  90. func UptimeWithContext(ctx context.Context) (uint64, error) {
  91. procGetTickCount := procGetTickCount64
  92. err := procGetTickCount64.Find()
  93. if err != nil {
  94. procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
  95. }
  96. r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0)
  97. if lastErr != 0 {
  98. return 0, lastErr
  99. }
  100. return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil
  101. }
  102. // cachedBootTime must be accessed via atomic.Load/StoreUint64
  103. var cachedBootTime uint64
  104. func BootTimeWithContext(ctx context.Context) (uint64, error) {
  105. t := atomic.LoadUint64(&cachedBootTime)
  106. if t != 0 {
  107. return t, nil
  108. }
  109. up, err := Uptime()
  110. if err != nil {
  111. return 0, err
  112. }
  113. t = timeSince(up)
  114. atomic.StoreUint64(&cachedBootTime, t)
  115. return t, nil
  116. }
  117. func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
  118. // GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
  119. // RtlGetVersion bypasses this lying layer and returns the true Windows version
  120. // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
  121. // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
  122. var osInfo osVersionInfoExW
  123. osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo))
  124. ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo)))
  125. if ret != 0 {
  126. return
  127. }
  128. // Platform
  129. var h windows.Handle // like HostIDWithContext(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx
  130. err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h)
  131. if err != nil {
  132. return
  133. }
  134. defer windows.RegCloseKey(h)
  135. var bufLen uint32
  136. var valType uint32
  137. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, nil, &bufLen)
  138. if err != nil {
  139. return
  140. }
  141. regBuf := make([]uint16, bufLen/2+1)
  142. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
  143. if err != nil {
  144. return
  145. }
  146. platform = windows.UTF16ToString(regBuf[:])
  147. if strings.Contains(platform, "Windows 10") { // check build number to determine whether it's actually Windows 11
  148. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CurrentBuildNumber`), nil, &valType, nil, &bufLen)
  149. if err == nil {
  150. regBuf = make([]uint16, bufLen/2+1)
  151. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CurrentBuildNumber`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
  152. if err == nil {
  153. buildNumberStr := windows.UTF16ToString(regBuf[:])
  154. if buildNumber, err := strconv.Atoi(buildNumberStr); err == nil && buildNumber >= 22000 {
  155. platform = strings.Replace(platform, "Windows 10", "Windows 11", 1)
  156. }
  157. }
  158. }
  159. }
  160. if !strings.HasPrefix(platform, "Microsoft") {
  161. platform = "Microsoft " + platform
  162. }
  163. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, nil, &bufLen) // append Service Pack number, only on success
  164. if err == nil { // don't return an error if only the Service Pack retrieval fails
  165. regBuf = make([]uint16, bufLen/2+1)
  166. err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
  167. if err == nil {
  168. platform += " " + windows.UTF16ToString(regBuf[:])
  169. }
  170. }
  171. // PlatformFamily
  172. switch osInfo.wProductType {
  173. case 1:
  174. family = "Standalone Workstation"
  175. case 2:
  176. family = "Server (Domain Controller)"
  177. case 3:
  178. family = "Server"
  179. }
  180. // Platform Version
  181. version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber)
  182. return platform, family, version, nil
  183. }
  184. func UsersWithContext(ctx context.Context) ([]UserStat, error) {
  185. var ret []UserStat
  186. return ret, common.ErrNotImplementedError
  187. }
  188. func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
  189. var ret []TemperatureStat
  190. var dst []msAcpi_ThermalZoneTemperature
  191. q := wmi.CreateQuery(&dst, "")
  192. if err := common.WMIQueryWithContext(ctx, q, &dst, nil, "root/wmi"); err != nil {
  193. return ret, err
  194. }
  195. for _, v := range dst {
  196. ts := TemperatureStat{
  197. SensorKey: v.InstanceName,
  198. Temperature: kelvinToCelsius(v.CurrentTemperature, 2),
  199. }
  200. ret = append(ret, ts)
  201. }
  202. return ret, nil
  203. }
  204. func kelvinToCelsius(temp uint32, n int) float64 {
  205. // wmi return temperature Kelvin * 10, so need to divide the result by 10,
  206. // and then minus 273.15 to get °Celsius.
  207. t := float64(temp/10) - 273.15
  208. n10 := math.Pow10(n)
  209. return math.Trunc((t+0.5/n10)*n10) / n10
  210. }
  211. func VirtualizationWithContext(ctx context.Context) (string, string, error) {
  212. return "", "", common.ErrNotImplementedError
  213. }
  214. func KernelVersionWithContext(ctx context.Context) (string, error) {
  215. _, _, version, err := PlatformInformationWithContext(ctx)
  216. return version, err
  217. }
  218. func KernelArch() (string, error) {
  219. var systemInfo systemInfo
  220. procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo)))
  221. const (
  222. PROCESSOR_ARCHITECTURE_INTEL = 0
  223. PROCESSOR_ARCHITECTURE_ARM = 5
  224. PROCESSOR_ARCHITECTURE_ARM64 = 12
  225. PROCESSOR_ARCHITECTURE_IA64 = 6
  226. PROCESSOR_ARCHITECTURE_AMD64 = 9
  227. )
  228. switch systemInfo.wProcessorArchitecture {
  229. case PROCESSOR_ARCHITECTURE_INTEL:
  230. if systemInfo.wProcessorLevel < 3 {
  231. return "i386", nil
  232. }
  233. if systemInfo.wProcessorLevel > 6 {
  234. return "i686", nil
  235. }
  236. return fmt.Sprintf("i%d86", systemInfo.wProcessorLevel), nil
  237. case PROCESSOR_ARCHITECTURE_ARM:
  238. return "arm", nil
  239. case PROCESSOR_ARCHITECTURE_ARM64:
  240. return "aarch64", nil
  241. case PROCESSOR_ARCHITECTURE_IA64:
  242. return "ia64", nil
  243. case PROCESSOR_ARCHITECTURE_AMD64:
  244. return "x86_64", nil
  245. }
  246. return "", nil
  247. }