github.com/gofiber/fiber/v2@v2.47.0/internal/gopsutil/cpu/cpu_windows.go (about)

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