...

Source file src/github.com/shirou/gopsutil/host/host_windows.go

Documentation: github.com/shirou/gopsutil/host

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

View as plain text