Merge pull request #537 from Lomanic/winapihost

[host][windows] Remove (slow) WMI calls, rely on registry or win32 API to get OS infos
This commit is contained in:
Lomanic 2018-06-25 10:11:43 +02:00 committed by GitHub
commit 4a180b209f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 71 additions and 44 deletions

View File

@ -9,10 +9,10 @@ import (
"runtime"
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/StackExchange/wmi"
"github.com/shirou/gopsutil/internal/common"
process "github.com/shirou/gopsutil/process"
"golang.org/x/sys/windows"
@ -20,15 +20,24 @@ import (
var (
procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
osInfo *Win32_OperatingSystem
procGetTickCount32 = common.Modkernel32.NewProc("GetTickCount")
procGetTickCount64 = common.Modkernel32.NewProc("GetTickCount64")
procRtlGetVersion = common.ModNt.NewProc("RtlGetVersion")
)
type Win32_OperatingSystem struct {
Version string
Caption string
ProductType uint32
BuildNumber string
LastBootUpTime time.Time
// 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) {
@ -84,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 {
@ -111,40 +122,24 @@ 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, "")
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())
}
func UptimeWithContext(ctx context.Context) (uint64, error) {
if osInfo == nil {
_, err := GetOSInfoWithContext(ctx)
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
}
@ -164,7 +159,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
}
@ -174,18 +169,50 @@ 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 = GetOSInfoWithContext(ctx)
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, " ")
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 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(&regBuf[0])), &bufLen)
if err != nil {
return
}
platform = windows.UTF16ToString(regBuf[:])
if !strings.HasPrefix(platform, "Microsoft") {
platform = "Microsoft " + platform
}
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(&regBuf[0])), &bufLen)
if err == nil {
platform += " " + windows.UTF16ToString(regBuf[:])
}
}
// PlatformFamily
switch osInfo.ProductType {
switch osInfo.wProductType {
case 1:
family = "Standalone Workstation"
case 2:
@ -195,9 +222,9 @@ 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
return platform, family, version, nil
}
func Users() ([]UserStat, error) {