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