Repository: cilium/ebpf
Pull request #1677 (2025-02-12 14:08:25)

ebpf: implement PossibleCPU on Windows
Move the Linux implementation into internal/linux and add a wrapper around the Windows GetMaximumProcessorCount function.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b862367..fb56fb1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,8 +10,8 @@ env:
   CI_MAX_KERNEL_VERSION: '6.11'
   CI_MAX_EFW_VERSION: '0.20.0'
   CI_MIN_CLANG_VERSION: '11'
-  go_version: '~1.23'
-  prev_go_version: '~1.22'
+  go_version: '~1.24'
+  prev_go_version: '~1.23'
   CGO_ENABLED: '0'
   # Sync with Pipfile and netlify.toml.
   python_version: '~3.13'
@@ -34,7 +34,7 @@ jobs:
           go-version: '${{ env.go_version }}'
 
       - name: Run golangci-lint
-        uses: golangci/golangci-lint-action@v6.2.0
+        uses: golangci/golangci-lint-action@v6.3.0
         with:
           args: "--out-format colored-line-number"
 
diff --git a/cpu.go b/cpu.go
index 07e959e..3bcdc38 100644
--- a/cpu.go
+++ b/cpu.go
@@ -1,16 +1,5 @@
 package ebpf
 
-import (
-	"fmt"
-	"os"
-	"strings"
-	"sync"
-)
-
-var possibleCPU = sync.OnceValues(func() (int, error) {
-	return parseCPUsFromFile("/sys/devices/system/cpu/possible")
-})
-
 // PossibleCPU returns the max number of CPUs a system may possibly have
 // Logical CPU numbers must be of the form 0-n
 func PossibleCPU() (int, error) {
@@ -26,41 +15,3 @@ func MustPossibleCPU() int {
 	}
 	return cpus
 }
-
-func parseCPUsFromFile(path string) (int, error) {
-	spec, err := os.ReadFile(path)
-	if err != nil {
-		return 0, err
-	}
-
-	n, err := parseCPUs(string(spec))
-	if err != nil {
-		return 0, fmt.Errorf("can't parse %s: %v", path, err)
-	}
-
-	return n, nil
-}
-
-// parseCPUs parses the number of cpus from a string produced
-// by bitmap_list_string() in the Linux kernel.
-// Multiple ranges are rejected, since they can't be unified
-// into a single number.
-// This is the format of /sys/devices/system/cpu/possible, it
-// is not suitable for /sys/devices/system/cpu/online, etc.
-func parseCPUs(spec string) (int, error) {
-	if strings.Trim(spec, "\n") == "0" {
-		return 1, nil
-	}
-
-	var low, high int
-	n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
-	if n != 2 || err != nil {
-		return 0, fmt.Errorf("invalid format: %s", spec)
-	}
-	if low != 0 {
-		return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
-	}
-
-	// cpus is 0 indexed
-	return high + 1, nil
-}
diff --git a/cpu_other.go b/cpu_other.go
new file mode 100644
index 0000000..eca5164
--- /dev/null
+++ b/cpu_other.go
@@ -0,0 +1,13 @@
+//go:build !windows
+
+package ebpf
+
+import (
+	"sync"
+
+	"github.com/cilium/ebpf/internal/linux"
+)
+
+var possibleCPU = sync.OnceValues(func() (int, error) {
+	return linux.ParseCPUsFromFile("/sys/devices/system/cpu/possible")
+})
diff --git a/cpu_windows.go b/cpu_windows.go
new file mode 100644
index 0000000..9448b09
--- /dev/null
+++ b/cpu_windows.go
@@ -0,0 +1,11 @@
+package ebpf
+
+import (
+	"sync"
+
+	"golang.org/x/sys/windows"
+)
+
+var possibleCPU = sync.OnceValues(func() (int, error) {
+	return int(windows.GetMaximumProcessorCount(windows.ALL_PROCESSOR_GROUPS)), nil
+})
diff --git a/go.mod b/go.mod
index 439844a..24891f9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/cilium/ebpf
 
-go 1.22
+go 1.23
 
 require (
 	github.com/go-quicktest/qt v1.101.0
diff --git a/internal/linux/cpu.go b/internal/linux/cpu.go
new file mode 100644
index 0000000..bd55ac9
--- /dev/null
+++ b/internal/linux/cpu.go
@@ -0,0 +1,45 @@
+package linux
+
+import (
+	"fmt"
+	"os"
+	"strings"
+)
+
+func ParseCPUsFromFile(path string) (int, error) {
+	spec, err := os.ReadFile(path)
+	if err != nil {
+		return 0, err
+	}
+
+	n, err := parseCPUs(string(spec))
+	if err != nil {
+		return 0, fmt.Errorf("can't parse %s: %v", path, err)
+	}
+
+	return n, nil
+}
+
+// parseCPUs parses the number of cpus from a string produced
+// by bitmap_list_string() in the Linux kernel.
+// Multiple ranges are rejected, since they can't be unified
+// into a single number.
+// This is the format of /sys/devices/system/cpu/possible, it
+// is not suitable for /sys/devices/system/cpu/online, etc.
+func parseCPUs(spec string) (int, error) {
+	if strings.Trim(spec, "\n") == "0" {
+		return 1, nil
+	}
+
+	var low, high int
+	n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
+	if n != 2 || err != nil {
+		return 0, fmt.Errorf("invalid format: %s", spec)
+	}
+	if low != 0 {
+		return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
+	}
+
+	// cpus is 0 indexed
+	return high + 1, nil
+}


Test patch:
diff --git a/cpu_test.go b/cpu_test.go
index fa5cdcf..223ba3f 100644
--- a/cpu_test.go
+++ b/cpu_test.go
@@ -2,31 +2,12 @@ package ebpf
 
 import (
 	"testing"
-)
 
-func TestParseCPUs(t *testing.T) {
-	for str, result := range map[string]int{
-		"0-1":   2,
-		"0-2\n": 3,
-		"0":     1,
-	} {
-		n, err := parseCPUs(str)
-		if err != nil {
-			t.Errorf("Can't parse `%s`: %v", str, err)
-		} else if n != result {
-			t.Error("Parsing", str, "returns", n, "instead of", result)
-		}
-	}
+	"github.com/go-quicktest/qt"
+)
 
-	for _, str := range []string{
-		"0,3-4",
-		"0-",
-		"1,",
-		"",
-	} {
-		_, err := parseCPUs(str)
-		if err == nil {
-			t.Error("Parsed invalid format:", str)
-		}
-	}
+func TestPossibleCPU(t *testing.T) {
+	num, err := PossibleCPU()
+	qt.Assert(t, qt.IsNil(err))
+	qt.Assert(t, qt.IsTrue(num > 0))
 }
diff --git a/internal/linux/cpu_test.go b/internal/linux/cpu_test.go
new file mode 100644
index 0000000..cad9d4d
--- /dev/null
+++ b/internal/linux/cpu_test.go
@@ -0,0 +1,32 @@
+package linux
+
+import (
+	"testing"
+)
+
+func TestParseCPUs(t *testing.T) {
+	for str, result := range map[string]int{
+		"0-1":   2,
+		"0-2\n": 3,
+		"0":     1,
+	} {
+		n, err := parseCPUs(str)
+		if err != nil {
+			t.Errorf("Can't parse `%s`: %v", str, err)
+		} else if n != result {
+			t.Error("Parsing", str, "returns", n, "instead of", result)
+		}
+	}
+
+	for _, str := range []string{
+		"0,3-4",
+		"0-",
+		"1,",
+		"",
+	} {
+		_, err := parseCPUs(str)
+		if err == nil {
+			t.Error("Parsed invalid format:", str)
+		}
+	}
+}