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