From df331c76153174232a72ac4d50674356c34660b5 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Wed, 6 Jun 2018 00:09:49 +0200 Subject: [PATCH 1/5] [host][windows] Use win32 API to get host's uptime instead of (slow) wmi --- host/host_windows.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/host/host_windows.go b/host/host_windows.go index 643bd66..c26370e 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -9,6 +9,7 @@ import ( "runtime" "strings" "sync/atomic" + "syscall" "time" "unsafe" @@ -20,6 +21,8 @@ import ( var ( procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime") + procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount") + procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64") osInfo *Win32_OperatingSystem ) @@ -28,7 +31,6 @@ type Win32_OperatingSystem struct { Caption string ProductType uint32 BuildNumber string - LastBootUpTime time.Time } func Info() (*InfoStat, error) { @@ -135,18 +137,19 @@ func Uptime() (uint64, error) { } func UptimeWithContext(ctx context.Context) (uint64, error) { - if osInfo == nil { - _, err := GetOSInfo() - if err != nil { - return 0, err - } + procGetTickCount := procGetTickCount64 + err := procGetTickCount64.Find() + if err != nil { + 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 } - now := time.Now() - t := osInfo.LastBootUpTime.Local() - return uint64(now.Sub(t).Seconds()), nil + r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0) + if lastErr != 0 { + return 0, lastErr + } + return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil } -func bootTime(up uint64) uint64 { +func bootTimeFromUptime(up uint64) uint64 { return uint64(time.Now().Unix()) - up } @@ -166,7 +169,7 @@ func BootTimeWithContext(ctx context.Context) (uint64, error) { if err != nil { return 0, err } - t = bootTime(up) + t = bootTimeFromUptime(up) atomic.StoreUint64(&cachedBootTime, t) return t, nil } From eb11fd6e2faa4559a81c057d9a62acf36dadf200 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 17 Jun 2018 23:41:32 +0200 Subject: [PATCH 2/5] [host][windows] Completely remove slow wmi calls to retrieve OS version, use registry and RtlGetVersion --- host/host_windows.go | 76 ++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/host/host_windows.go b/host/host_windows.go index c26370e..6fe6142 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -13,24 +13,32 @@ import ( "time" "unsafe" - "github.com/StackExchange/wmi" "github.com/shirou/gopsutil/internal/common" process "github.com/shirou/gopsutil/process" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" ) var ( procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime") procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount") procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64") - osInfo *Win32_OperatingSystem + procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion") ) -type Win32_OperatingSystem struct { - Version string - Caption string - ProductType uint32 - BuildNumber string +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw +type osVersionInfoExW struct { + dwOSVersionInfoSize uint32 + dwMajorVersion uint32 + dwMinorVersion uint32 + dwBuildNumber uint32 + dwPlatformId uint32 + szCSDVersion [128]uint16 + wServicePackMajor uint16 + wServicePackMinor uint16 + wSuiteMask uint16 + wProductType uint8 + wReserved uint8 } func Info() (*InfoStat, error) { @@ -113,25 +121,6 @@ func getMachineGuid() (string, error) { return hostID, nil } -func GetOSInfo() (Win32_OperatingSystem, error) { - return GetOSInfoWithContext(context.Background()) -} - -func GetOSInfoWithContext(ctx context.Context) (Win32_OperatingSystem, error) { - var dst []Win32_OperatingSystem - q := wmi.CreateQuery(&dst, "") - ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) - defer cancel() - err := common.WMIQueryWithContext(ctx, q, &dst) - if err != nil { - return Win32_OperatingSystem{}, err - } - - osInfo = &dst[0] - - return dst[0], nil -} - func Uptime() (uint64, error) { return UptimeWithContext(context.Background()) } @@ -179,18 +168,37 @@ func PlatformInformation() (platform string, family string, version string, err } func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) { - if osInfo == nil { - _, err = GetOSInfo() - if err != nil { - return - } + // GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest + // RtlGetVersion bypasses this lying layer and returns the true Windows version + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw + var osInfo osVersionInfoExW + osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo)) + ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo))) + if ret != 0 { + return } // Platform - platform = strings.Trim(osInfo.Caption, " ") + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return + } + defer k.Close() + platform, _, err = k.GetStringValue("ProductName") + if err != nil { + return + } + if !strings.HasPrefix(platform, "Microsoft") { + platform = "Microsoft " + platform + } + csd, _, err := k.GetStringValue("CSDVersion") + if err == nil { + platform += " " + csd + } // PlatformFamily - switch osInfo.ProductType { + switch osInfo.wProductType { case 1: family = "Standalone Workstation" case 2: @@ -200,7 +208,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil } // Platform Version - version = fmt.Sprintf("%s Build %s", osInfo.Version, osInfo.BuildNumber) + version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber) return } From c729bbd6a8e6fd243e2f43c24666c29feb3e6294 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Sun, 17 Jun 2018 23:48:45 +0200 Subject: [PATCH 3/5] [host][windows] Refactor code to query registry via golang.org/x/sys/windows/registry --- host/host_windows.go | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/host/host_windows.go b/host/host_windows.go index 6fe6142..1c65c5c 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -15,7 +15,6 @@ import ( "github.com/shirou/gopsutil/internal/common" process "github.com/shirou/gopsutil/process" - "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -94,30 +93,15 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { } func getMachineGuid() (string, error) { - var h windows.Handle - err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE) if err != nil { return "", err } - defer windows.RegCloseKey(h) - - const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 - const uuidLen = 36 - - var regBuf [windowsRegBufLen]uint16 - bufLen := uint32(windowsRegBufLen) - var valType uint32 - err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + defer k.Close() + hostID, _, err := k.GetStringValue("MachineGuid") if err != nil { return "", err } - - hostID := windows.UTF16ToString(regBuf[:]) - hostIDLen := len(hostID) - if hostIDLen != uuidLen { - return "", fmt.Errorf("HostID incorrect: %q\n", hostID) - } - return hostID, nil } From d8a6a50129bd0ce6ab93c1a73f4be0ebd9b30b25 Mon Sep 17 00:00:00 2001 From: Lomanic Date: Mon, 18 Jun 2018 22:17:31 +0200 Subject: [PATCH 4/5] Revert "[host][windows] Refactor code to query registry via golang.org/x/sys/windows/registry" This reverts commit c729bbd6a8e6fd243e2f43c24666c29feb3e6294. --- host/host_windows.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/host/host_windows.go b/host/host_windows.go index 1c65c5c..6fe6142 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -15,6 +15,7 @@ import ( "github.com/shirou/gopsutil/internal/common" process "github.com/shirou/gopsutil/process" + "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -93,15 +94,30 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { } func getMachineGuid() (string, error) { - k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE) + var h windows.Handle + err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) if err != nil { return "", err } - defer k.Close() - hostID, _, err := k.GetStringValue("MachineGuid") + defer windows.RegCloseKey(h) + + const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 + const uuidLen = 36 + + var regBuf [windowsRegBufLen]uint16 + bufLen := uint32(windowsRegBufLen) + var valType uint32 + err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) if err != nil { return "", err } + + hostID := windows.UTF16ToString(regBuf[:]) + hostIDLen := len(hostID) + if hostIDLen != uuidLen { + return "", fmt.Errorf("HostID incorrect: %q\n", hostID) + } + return hostID, nil } From 61e2d92d952c899619b03f390d4720567ec3d52f Mon Sep 17 00:00:00 2001 From: Lomanic Date: Mon, 18 Jun 2018 23:03:39 +0200 Subject: [PATCH 5/5] [host][windows] Query registry via golang.org/x/sys/windows instead of golang.org/x/sys/windows/registry See https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612 --- host/host_windows.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/host/host_windows.go b/host/host_windows.go index 6fe6142..123eddf 100644 --- a/host/host_windows.go +++ b/host/host_windows.go @@ -16,7 +16,6 @@ import ( "github.com/shirou/gopsutil/internal/common" process "github.com/shirou/gopsutil/process" "golang.org/x/sys/windows" - "golang.org/x/sys/windows/registry" ) var ( @@ -94,6 +93,8 @@ func InfoWithContext(ctx context.Context) (*InfoStat, error) { } func getMachineGuid() (string, error) { + // 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 + // for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue var h windows.Handle err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) if err != nil { @@ -180,21 +181,34 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil } // Platform - k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + var h windows.Handle // like getMachineGuid(), we query the registry using the raw windows.RegOpenKeyEx/RegQueryValueEx + err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) if err != nil { return } - defer k.Close() - platform, _, err = k.GetStringValue("ProductName") + defer windows.RegCloseKey(h) + var bufLen uint32 + var valType uint32 + err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, nil, &bufLen) if err != nil { return } + regBuf := make([]uint16, bufLen/2+1) + err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`ProductName`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + if err != nil { + return + } + platform = windows.UTF16ToString(regBuf[:]) if !strings.HasPrefix(platform, "Microsoft") { platform = "Microsoft " + platform } - csd, _, err := k.GetStringValue("CSDVersion") - if err == nil { - platform += " " + csd + err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, nil, &bufLen) // append Service Pack number, only on success + if err == nil { // don't return an error if only the Service Pack retrieval fails + regBuf = make([]uint16, bufLen/2+1) + err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`CSDVersion`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + if err == nil { + platform += " " + windows.UTF16ToString(regBuf[:]) + } } // PlatformFamily @@ -210,7 +224,7 @@ func PlatformInformationWithContext(ctx context.Context) (platform string, famil // Platform Version version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber) - return + return platform, family, version, nil } func Users() ([]UserStat, error) {