2021-12-22 21:54:41 +00:00
//go:build windows
2014-04-18 21:28:00 +09:00
// +build windows
2014-12-30 22:09:05 +09:00
package cpu
2014-04-18 21:28:00 +09:00
import (
2017-11-09 15:44:26 -08:00
"context"
2015-04-20 00:05:31 +09:00
"fmt"
2014-04-19 01:29:34 +09:00
"unsafe"
2014-11-27 22:28:05 +09:00
2021-11-09 13:14:54 +00:00
"github.com/shirou/gopsutil/v3/internal/common"
2021-12-22 21:46:33 +00:00
"github.com/yusufpapurcu/wmi"
2017-06-02 13:51:00 -07:00
"golang.org/x/sys/windows"
2014-04-18 21:28:00 +09:00
)
2019-05-08 20:34:59 +02:00
var (
procGetActiveProcessorCount = common . Modkernel32 . NewProc ( "GetActiveProcessorCount" )
procGetNativeSystemInfo = common . Modkernel32 . NewProc ( "GetNativeSystemInfo" )
)
2021-11-06 09:53:56 +00:00
type win32_Processor struct {
2015-04-20 00:05:31 +09:00
Family uint16
Manufacturer string
Name string
NumberOfLogicalProcessors uint32
2019-08-14 23:52:41 +02:00
NumberOfCores uint32
2016-04-01 21:34:39 +09:00
ProcessorID * string
2015-04-20 00:05:31 +09:00
Stepping * string
MaxClockSpeed uint32
}
2019-02-21 23:26:14 +03:00
// SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
// defined in windows api doc with the following
// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntquerysysteminformation#system_processor_performance_information
// additional fields documented here
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/processor_performance.htm
type win32_SystemProcessorPerformanceInformation struct {
IdleTime int64 // idle time in 100ns (this is not a filetime).
KernelTime int64 // kernel time in 100ns. kernel time includes idle time. (this is not a filetime).
UserTime int64 // usertime in 100ns (this is not a filetime).
DpcTime int64 // dpc time in 100ns (this is not a filetime).
InterruptTime int64 // interrupt time in 100ns
InterruptCount uint32
2018-11-18 22:30:47 -08:00
}
2018-11-19 21:51:27 -08:00
const (
2020-05-12 15:36:05 +02:00
ClocksPerSec = 10000000.0
2019-02-21 23:26:14 +03:00
// systemProcessorPerformanceInformationClass information class to query with NTQuerySystemInformation
// https://processhacker.sourceforge.io/doc/ntexapi_8h.html#ad5d815b48e8f4da1ef2eb7a2f18a54e0
win32_SystemProcessorPerformanceInformationClass = 8
// size of systemProcessorPerformanceInfoSize in memory
win32_SystemProcessorPerformanceInfoSize = uint32 ( unsafe . Sizeof ( win32_SystemProcessorPerformanceInformation { } ) )
2018-11-19 21:51:27 -08:00
)
2017-06-05 15:46:35 +05:30
// Times returns times stat per cpu and combined for all CPUs
2016-03-22 23:09:12 +09:00
func Times ( percpu bool ) ( [ ] TimesStat , error ) {
2017-12-31 15:25:49 +09:00
return TimesWithContext ( context . Background ( ) , percpu )
}
func TimesWithContext ( ctx context . Context , percpu bool ) ( [ ] TimesStat , error ) {
2017-06-05 14:18:20 +05:30
if percpu {
2019-02-22 11:19:56 +03:00
return perCPUTimes ( )
2017-06-05 14:18:20 +05:30
}
2014-04-18 21:28:00 +09:00
2017-06-05 14:18:20 +05:30
var ret [ ] TimesStat
2014-11-27 22:28:05 +09:00
var lpIdleTime common . FILETIME
var lpKernelTime common . FILETIME
var lpUserTime common . FILETIME
r , _ , _ := common . ProcGetSystemTimes . Call (
2014-04-19 01:29:34 +09:00
uintptr ( unsafe . Pointer ( & lpIdleTime ) ) ,
uintptr ( unsafe . Pointer ( & lpKernelTime ) ) ,
uintptr ( unsafe . Pointer ( & lpUserTime ) ) )
if r == 0 {
2017-06-02 13:51:00 -07:00
return ret , windows . GetLastError ( )
2014-04-19 01:29:34 +09:00
}
2014-04-30 15:32:05 +09:00
LOT := float64 ( 0.0000001 )
HIT := ( LOT * 4294967296.0 )
idle := ( ( HIT * float64 ( lpIdleTime . DwHighDateTime ) ) + ( LOT * float64 ( lpIdleTime . DwLowDateTime ) ) )
user := ( ( HIT * float64 ( lpUserTime . DwHighDateTime ) ) + ( LOT * float64 ( lpUserTime . DwLowDateTime ) ) )
kernel := ( ( HIT * float64 ( lpKernelTime . DwHighDateTime ) ) + ( LOT * float64 ( lpKernelTime . DwLowDateTime ) ) )
2014-04-19 01:29:34 +09:00
system := ( kernel - idle )
2016-03-22 23:09:12 +09:00
ret = append ( ret , TimesStat {
2017-07-20 11:47:15 +08:00
CPU : "cpu-total" ,
2015-02-13 22:45:12 +09:00
Idle : float64 ( idle ) ,
User : float64 ( user ) ,
System : float64 ( system ) ,
2014-04-19 01:29:34 +09:00
} )
2014-04-18 21:28:00 +09:00
return ret , nil
}
2014-05-16 18:42:37 +09:00
2016-03-22 23:09:12 +09:00
func Info ( ) ( [ ] InfoStat , error ) {
2017-12-31 15:25:49 +09:00
return InfoWithContext ( context . Background ( ) )
}
func InfoWithContext ( ctx context . Context ) ( [ ] InfoStat , error ) {
2016-03-22 23:09:12 +09:00
var ret [ ] InfoStat
2021-11-06 09:53:56 +00:00
var dst [ ] win32_Processor
2015-04-20 00:05:31 +09:00
q := wmi . CreateQuery ( & dst , "" )
2017-11-09 15:44:26 -08:00
if err := common . WMIQueryWithContext ( ctx , q , & dst ) ; err != nil {
2015-02-15 21:25:33 +09:00
return ret , err
}
2015-09-03 21:48:18 +09:00
2015-08-28 06:56:05 +01:00
var procID string
2015-09-03 21:48:18 +09:00
for i , l := range dst {
2015-08-28 06:56:05 +01:00
procID = ""
2016-04-01 21:34:39 +09:00
if l . ProcessorID != nil {
procID = * l . ProcessorID
2015-08-28 06:56:05 +01:00
}
2015-09-03 21:48:18 +09:00
2016-03-22 23:09:12 +09:00
cpu := InfoStat {
2015-02-15 21:25:33 +09:00
CPU : int32 ( i ) ,
2015-04-20 00:05:31 +09:00
Family : fmt . Sprintf ( "%d" , l . Family ) ,
VendorID : l . Manufacturer ,
ModelName : l . Name ,
Cores : int32 ( l . NumberOfLogicalProcessors ) ,
2015-08-25 22:21:10 -07:00
PhysicalID : procID ,
2015-04-20 00:05:31 +09:00
Mhz : float64 ( l . MaxClockSpeed ) ,
2015-02-15 21:25:33 +09:00
Flags : [ ] string { } ,
}
ret = append ( ret , cpu )
}
2015-04-20 00:05:31 +09:00
2014-05-16 18:42:37 +09:00
return ret , nil
}
2017-05-25 17:54:43 +05:30
2017-06-05 15:46:35 +05:30
// perCPUTimes returns times stat per cpu, per core and overall for all CPUs
2019-02-22 11:19:56 +03:00
func perCPUTimes ( ) ( [ ] TimesStat , error ) {
2017-06-05 14:18:20 +05:30
var ret [ ] TimesStat
2019-02-22 11:19:56 +03:00
stats , err := perfInfo ( )
2017-06-05 14:18:20 +05:30
if err != nil {
return nil , err
}
2019-02-21 23:26:14 +03:00
for core , v := range stats {
2017-06-05 14:18:20 +05:30
c := TimesStat {
2019-02-21 23:26:14 +03:00
CPU : fmt . Sprintf ( "cpu%d" , core ) ,
2020-05-12 15:36:05 +02:00
User : float64 ( v . UserTime ) / ClocksPerSec ,
System : float64 ( v . KernelTime - v . IdleTime ) / ClocksPerSec ,
Idle : float64 ( v . IdleTime ) / ClocksPerSec ,
Irq : float64 ( v . InterruptTime ) / ClocksPerSec ,
2017-06-05 14:18:20 +05:30
}
ret = append ( ret , c )
}
return ret , nil
}
2019-02-21 23:26:14 +03:00
// makes call to Windows API function to retrieve performance information for each core
2019-02-22 11:19:56 +03:00
func perfInfo ( ) ( [ ] win32_SystemProcessorPerformanceInformation , error ) {
2019-02-21 23:26:14 +03:00
// Make maxResults large for safety.
// We can't invoke the api call with a results array that's too small.
// If we have more than 2056 cores on a single host, then it's probably the future.
maxBuffer := 2056
// buffer for results from the windows proc
resultBuffer := make ( [ ] win32_SystemProcessorPerformanceInformation , maxBuffer )
// size of the buffer in memory
bufferSize := uintptr ( win32_SystemProcessorPerformanceInfoSize ) * uintptr ( maxBuffer )
// size of the returned response
var retSize uint32
// Invoke windows api proc.
// The returned err from the windows dll proc will always be non-nil even when successful.
// See https://godoc.org/golang.org/x/sys/windows#LazyProc.Call for more information
retCode , _ , err := common . ProcNtQuerySystemInformation . Call (
win32_SystemProcessorPerformanceInformationClass , // System Information Class -> SystemProcessorPerformanceInformation
uintptr ( unsafe . Pointer ( & resultBuffer [ 0 ] ) ) , // pointer to first element in result buffer
bufferSize , // size of the buffer in memory
uintptr ( unsafe . Pointer ( & retSize ) ) , // pointer to the size of the returned results the windows proc will set this
)
// check return code for errors
if retCode != 0 {
return nil , fmt . Errorf ( "call to NtQuerySystemInformation returned %d. err: %s" , retCode , err . Error ( ) )
}
// calculate the number of returned elements based on the returned size
numReturnedElements := retSize / win32_SystemProcessorPerformanceInfoSize
// trim results to the number of returned elements
resultBuffer = resultBuffer [ : numReturnedElements ]
return resultBuffer , nil
}
2019-03-03 14:38:33 +01:00
2019-05-08 20:34:59 +02:00
// SystemInfo is an equivalent representation of SYSTEM_INFO in the Windows API.
// https://msdn.microsoft.com/en-us/library/ms724958%28VS.85%29.aspx?f=255&MSPPError=-2147217396
// https://github.com/elastic/go-windows/blob/bb1581babc04d5cb29a2bfa7a9ac6781c730c8dd/kernel32.go#L43
type systemInfo struct {
wProcessorArchitecture uint16
wReserved uint16
dwPageSize uint32
lpMinimumApplicationAddress uintptr
lpMaximumApplicationAddress uintptr
dwActiveProcessorMask uintptr
dwNumberOfProcessors uint32
dwProcessorType uint32
dwAllocationGranularity uint32
wProcessorLevel uint16
wProcessorRevision uint16
}
2019-03-03 14:38:33 +01:00
func CountsWithContext ( ctx context . Context , logical bool ) ( int , error ) {
2019-05-08 20:34:59 +02:00
if logical {
// https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97
err := procGetActiveProcessorCount . Find ( )
if err == nil { // Win7+
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
if ret != 0 {
return int ( ret ) , nil
}
}
var systemInfo systemInfo
_ , _ , err = procGetNativeSystemInfo . Call ( uintptr ( unsafe . Pointer ( & systemInfo ) ) )
if systemInfo . dwNumberOfProcessors == 0 {
return 0 , err
}
return int ( systemInfo . dwNumberOfProcessors ) , nil
}
// physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499
2019-08-14 23:52:41 +02:00
// for the time being, try with unreliable and slow WMI call…
2021-11-06 09:53:56 +00:00
var dst [ ] win32_Processor
2019-08-14 23:52:41 +02:00
q := wmi . CreateQuery ( & dst , "" )
if err := common . WMIQueryWithContext ( ctx , q , & dst ) ; err != nil {
return 0 , err
}
var count uint32
for _ , d := range dst {
count += d . NumberOfCores
}
return int ( count ) , nil
2019-03-03 14:38:33 +01:00
}