From 1ebcd369abb6046f214105dcb7705ffef333302f Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Sun, 13 Apr 2025 17:47:36 +0200 Subject: [PATCH 1/7] implement get physical core api --- cpu/cpu_test.go | 27 ++++++++++++------ cpu/cpu_windows.go | 80 +++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index 4d9794b..b4662f5 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -59,16 +59,27 @@ func TestTimes(t *testing.T) { } func TestCounts(t *testing.T) { - v, err := Counts(true) + logicalCount, err := Counts(true) common.SkipIfNotImplementedErr(t, err) - require.NoError(t, err) - assert.NotZerof(t, v, "could not get logical CPU counts: %v", v) - t.Logf("logical cores: %d", v) - v, err = Counts(false) + if err != nil { + t.Errorf("error %v", err) + } + if logicalCount == 0 { + t.Errorf("could not get logical CPU counts: %v", logicalCount) + } + t.Logf("logical cores: %d", logicalCount) + physicalCount, err := Counts(false) common.SkipIfNotImplementedErr(t, err) - require.NoError(t, err) - assert.NotZerof(t, v, "could not get physical CPU counts: %v", v) - t.Logf("physical cores: %d", v) + if err != nil { + t.Errorf("error %v", err) + } + if physicalCount == 0 { + t.Errorf("could not get physical CPU counts: %v", physicalCount) + } + t.Logf("physical cores: %d", physicalCount) + if physicalCount > logicalCount { + t.Errorf("physical cpu cannot be more than logical cpu: %v > %v", physicalCount, logicalCount) + } } func TestTimeStat_String(t *testing.T) { diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index 3ca8281..aeed5fb 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -16,6 +16,7 @@ import ( ) var procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") +var getLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") type win32_Processor struct { //nolint:revive //FIXME Family uint16 @@ -200,13 +201,75 @@ type systemInfo struct { wProcessorRevision uint16 } +type groupAffinity struct { + mask uintptr // https://learn.microsoft.com/it-it/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity + group uint16 + reserved [3]uint16 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-processor_relationship +type processorRelationship struct { + flags byte + efficientClass byte + reserved [20]byte + groupCount uint16 + groupMask [1]groupAffinity +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex +type systemLogicalProcessorInformationEx struct { + Relationship int + Size uint32 + Processor processorRelationship +} + +func getPhysicalCoreCount(ctx context.Context) (int, error) { + var length uint32 + const relationAll = 0xffff + const relationProcessorCore = 0x0 + + // First call to determine the required buffer size + _, _, err := getLogicalProcessorInformationEx.Call(uintptr(relationAll), 0, uintptr(unsafe.Pointer(&length))) + if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { + return 0, fmt.Errorf("failed to get buffer size: %w", err) + } + + // Allocate the buffer + buffer := make([]byte, length) + + // Second call to retrieve the processor information + _, _, err = getLogicalProcessorInformationEx.Call(uintptr(relationAll), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length))) + if err != nil && err != windows.NTE_OP_OK { + return 0, fmt.Errorf("failed to get logical processor information: %w", err) + } + + // Iterate through the buffer to count physical cores + offset := uintptr(0) + ncpus := 0 + for offset < uintptr(length) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + info := (*systemLogicalProcessorInformationEx)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + offset)) + if info.Relationship == relationProcessorCore { + ncpus++ + } + offset += uintptr(info.Size) + } + } + + return ncpus, nil +} + func CountsWithContext(ctx context.Context, logical bool) (int, error) { if logical { - // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97 + // Get logical processor count ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS) if ret != 0 { return int(ret), nil } + var systemInfo systemInfo _, _, err := procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo))) if systemInfo.dwNumberOfProcessors == 0 { @@ -214,16 +277,7 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { } return int(systemInfo.dwNumberOfProcessors), nil } - // physical cores https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499 - // for the time being, try with unreliable and slow WMI call… - var dst []win32_Processor - 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 + + // Get physical core count + return getPhysicalCoreCount(ctx) } From b4446224fc168c50092cb81c35eecd1eb4e80f05 Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Sun, 13 Apr 2025 18:34:51 +0200 Subject: [PATCH 2/7] fix --- cpu/cpu_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index aeed5fb..afac603 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -218,7 +218,7 @@ type processorRelationship struct { // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_logical_processor_information_ex type systemLogicalProcessorInformationEx struct { - Relationship int + Relationship uint32 Size uint32 Processor processorRelationship } From 00a28a710eab61ff6697f967c8bf9ff435d6c0de Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Tue, 15 Apr 2025 06:20:24 +0200 Subject: [PATCH 3/7] lint --- cpu/cpu_windows.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index afac603..c75af6d 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -5,6 +5,7 @@ package cpu import ( "context" + "errors" "fmt" "strconv" "unsafe" @@ -15,8 +16,10 @@ import ( "github.com/shirou/gopsutil/v4/internal/common" ) -var procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") -var getLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") +var ( + procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") + getLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") +) type win32_Processor struct { //nolint:revive //FIXME Family uint16 @@ -230,7 +233,7 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { // First call to determine the required buffer size _, _, err := getLogicalProcessorInformationEx.Call(uintptr(relationAll), 0, uintptr(unsafe.Pointer(&length))) - if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { + if err != nil && !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) { return 0, fmt.Errorf("failed to get buffer size: %w", err) } @@ -239,7 +242,7 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { // Second call to retrieve the processor information _, _, err = getLogicalProcessorInformationEx.Call(uintptr(relationAll), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length))) - if err != nil && err != windows.NTE_OP_OK { + if err != nil && !errors.Is(err, windows.NTE_OP_OK) { return 0, fmt.Errorf("failed to get logical processor information: %w", err) } From b30f1523d89a4202a1f5c2804443ca9c0632e6a6 Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Wed, 16 Apr 2025 20:11:51 +0200 Subject: [PATCH 4/7] code review --- cpu/cpu_windows.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index c75af6d..3ccc7fa 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -17,8 +17,8 @@ import ( ) var ( - procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") - getLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") + procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") + procGetLogicalProcessorInformationEx = common.Modkernel32.NewProc("GetLogicalProcessorInformationEx") ) type win32_Processor struct { //nolint:revive //FIXME @@ -232,7 +232,7 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { const relationProcessorCore = 0x0 // First call to determine the required buffer size - _, _, err := getLogicalProcessorInformationEx.Call(uintptr(relationAll), 0, uintptr(unsafe.Pointer(&length))) + _, _, err := procGetLogicalProcessorInformationEx.Call(uintptr(relationAll), 0, uintptr(unsafe.Pointer(&length))) if err != nil && !errors.Is(err, windows.ERROR_INSUFFICIENT_BUFFER) { return 0, fmt.Errorf("failed to get buffer size: %w", err) } @@ -241,7 +241,7 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { buffer := make([]byte, length) // Second call to retrieve the processor information - _, _, err = getLogicalProcessorInformationEx.Call(uintptr(relationAll), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length))) + _, _, err = procGetLogicalProcessorInformationEx.Call(uintptr(relationAll), uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&length))) if err != nil && !errors.Is(err, windows.NTE_OP_OK) { return 0, fmt.Errorf("failed to get logical processor information: %w", err) } @@ -267,7 +267,7 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { func CountsWithContext(ctx context.Context, logical bool) (int, error) { if logical { - // Get logical processor count + // Get logical processor count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97 ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS) if ret != 0 { return int(ret), nil @@ -281,6 +281,6 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { return int(systemInfo.dwNumberOfProcessors), nil } - // Get physical core count + // Get physical core count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499 return getPhysicalCoreCount(ctx) } From d46c4a822d2acabbd4e22367b214918a59acd747 Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Thu, 17 Apr 2025 06:26:48 +0200 Subject: [PATCH 5/7] use testify --- cpu/cpu_test.go | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index b4662f5..9295dcd 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -61,25 +61,15 @@ func TestTimes(t *testing.T) { func TestCounts(t *testing.T) { logicalCount, err := Counts(true) common.SkipIfNotImplementedErr(t, err) - if err != nil { - t.Errorf("error %v", err) - } - if logicalCount == 0 { - t.Errorf("could not get logical CPU counts: %v", logicalCount) - } + require.NoError(t, err) + assert.NotZerof(t, logicalCount, "could not get logical CPU counts: %v", logicalCount) t.Logf("logical cores: %d", logicalCount) physicalCount, err := Counts(false) common.SkipIfNotImplementedErr(t, err) - if err != nil { - t.Errorf("error %v", err) - } - if physicalCount == 0 { - t.Errorf("could not get physical CPU counts: %v", physicalCount) - } + require.NoError(t, err) + assert.NotZerof(t, physicalCount, "could not get physical CPU counts: %v", physicalCount) t.Logf("physical cores: %d", physicalCount) - if physicalCount > logicalCount { - t.Errorf("physical cpu cannot be more than logical cpu: %v > %v", physicalCount, logicalCount) - } + assert.GreaterOrEqualf(t, logicalCount, physicalCount, "logical CPU count should be greater than or equal to physical CPU count: %v >= %v", logicalCount, physicalCount) } func TestTimeStat_String(t *testing.T) { From ab48e6b803b4f0f382f58ee25cd50d2f2370a1d8 Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Sun, 20 Apr 2025 07:27:31 +0200 Subject: [PATCH 6/7] remove context cancellation --- cpu/cpu_windows.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index 3ccc7fa..e32f6cb 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -226,7 +226,7 @@ type systemLogicalProcessorInformationEx struct { Processor processorRelationship } -func getPhysicalCoreCount(ctx context.Context) (int, error) { +func getPhysicalCoreCount() (int, error) { var length uint32 const relationAll = 0xffff const relationProcessorCore = 0x0 @@ -250,16 +250,11 @@ func getPhysicalCoreCount(ctx context.Context) (int, error) { offset := uintptr(0) ncpus := 0 for offset < uintptr(length) { - select { - case <-ctx.Done(): - return 0, ctx.Err() - default: - info := (*systemLogicalProcessorInformationEx)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + offset)) - if info.Relationship == relationProcessorCore { - ncpus++ - } - offset += uintptr(info.Size) + info := (*systemLogicalProcessorInformationEx)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + offset)) + if info.Relationship == relationProcessorCore { + ncpus++ } + offset += uintptr(info.Size) } return ncpus, nil @@ -282,5 +277,5 @@ func CountsWithContext(ctx context.Context, logical bool) (int, error) { } // Get physical core count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L499 - return getPhysicalCoreCount(ctx) + return getPhysicalCoreCount() } From f2b0aa5e8663ab669d7d554af09dfc447317bc83 Mon Sep 17 00:00:00 2001 From: Stefano Balzarotti Date: Thu, 1 May 2025 06:29:48 +0200 Subject: [PATCH 7/7] linter --- cpu/cpu_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cpu_windows.go b/cpu/cpu_windows.go index e32f6cb..f1e05b5 100644 --- a/cpu/cpu_windows.go +++ b/cpu/cpu_windows.go @@ -260,7 +260,7 @@ func getPhysicalCoreCount() (int, error) { return ncpus, nil } -func CountsWithContext(ctx context.Context, logical bool) (int, error) { +func CountsWithContext(_ context.Context, logical bool) (int, error) { if logical { // Get logical processor count https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_psutil_windows.c#L97 ret := windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS)