github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/cpu/cpu_windows.go (about)

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