Repository: prometheus/procfs
Pull request #427 (2021-12-15 12:43:55)

Add isolated cpu parsing
Hi
This PR adds support for parsing `/sys/devices/system/cpu/isolated`
Prompted by [this PR](https://github.com/prometheus/node_exporter/pull/2251) for node_exporter

I am not sure if this should be a `uint16` or not.

discordianfish: Beside that, looks good - especially for a first time go contribution 🥳 

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index e005ee9..5029418 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -6462,6 +6462,11 @@ Mode: 644
 Directory: fixtures/sys/devices/system/cpu/cpufreq/policy1
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/isolated
+Lines: 1
+1,2-7,9
+Mode: 664
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/node
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sysfs/system_cpu.go b/sysfs/system_cpu.go
index 40fc94b..7c46c2e 100644
--- a/sysfs/system_cpu.go
+++ b/sysfs/system_cpu.go
@@ -16,8 +16,11 @@
 package sysfs
 
 import (
+	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 
 	"golang.org/x/sync/errgroup"
@@ -237,3 +240,49 @@ func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
 		SetSpeed:                 stringOut[4],
 	}, nil
 }
+
+func (fs FS) IsolatedCPUs() ([]uint16, error) {
+	isolcpus, err := ioutil.ReadFile(fs.sys.Path("devices/system/cpu/isolated"))
+	if err != nil {
+		return nil, err
+	}
+
+	return parseIsolatedCPUs(isolcpus)
+}
+
+func parseIsolatedCPUs(data []byte) ([]uint16, error) {
+
+	var isolcpusInt = []uint16{}
+
+	for _, cpu := range strings.Split(strings.TrimRight(string(data), "\n"), ",") {
+		if cpu == "" {
+			continue
+		}
+		if strings.Contains(cpu, "-") {
+			ranges := strings.Split(cpu, "-")
+			if len(ranges) != 2 {
+				return nil, fmt.Errorf("invalid cpu range: %s", cpu)
+			}
+			startRange, err := strconv.Atoi(ranges[0])
+			if err != nil {
+				return nil, fmt.Errorf("invalid cpu start range: %w", err)
+			}
+			endRange, err := strconv.Atoi(ranges[1])
+			if err != nil {
+				return nil, fmt.Errorf("invalid cpu end range: %w", err)
+			}
+
+			for i := startRange; i <= endRange; i++ {
+				isolcpusInt = append(isolcpusInt, uint16(i))
+			}
+			continue
+		}
+
+		cpuN, err := strconv.Atoi(cpu)
+		if err != nil {
+			return nil, err
+		}
+		isolcpusInt = append(isolcpusInt, uint16(cpuN))
+	}
+	return isolcpusInt, nil
+}


Test patch:
diff --git a/sysfs/system_cpu_test.go b/sysfs/system_cpu_test.go
index 522b58b..0ae9f0f 100644
--- a/sysfs/system_cpu_test.go
+++ b/sysfs/system_cpu_test.go
@@ -16,6 +16,7 @@
 package sysfs
 
 import (
+	"errors"
 	"reflect"
 	"testing"
 )
@@ -140,3 +141,54 @@ func TestSystemCpufreq(t *testing.T) {
 		t.Errorf("Result not correct: want %v, have %v", systemCpufreq, c)
 	}
 }
+
+func TestIsolatedParsingCPU(t *testing.T) {
+	var testParams = []struct {
+		in  []byte
+		res []uint16
+		err error
+	}{
+		{[]byte(""), []uint16{}, nil},
+		{[]byte("1\n"), []uint16{1}, nil},
+		{[]byte("1"), []uint16{1}, nil},
+		{[]byte("1,2"), []uint16{1, 2}, nil},
+		{[]byte("1-2"), []uint16{1, 2}, nil},
+		{[]byte("1-3"), []uint16{1, 2, 3}, nil},
+		{[]byte("1,2-4"), []uint16{1, 2, 3, 4}, nil},
+		{[]byte("1,3-4"), []uint16{1, 3, 4}, nil},
+		{[]byte("1,3-4,7,20-21"), []uint16{1, 3, 4, 7, 20, 21}, nil},
+
+		{[]byte("1,"), []uint16{1}, nil},
+		{[]byte("1,2-"), nil, errors.New(`invalid cpu end range: strconv.Atoi: parsing "": invalid syntax`)},
+		{[]byte("1,-3"), nil, errors.New(`invalid cpu start range: strconv.Atoi: parsing "": invalid syntax`)},
+	}
+	for _, params := range testParams {
+		t.Run("blabla", func(t *testing.T) {
+			res, err := parseIsolatedCPUs(params.in)
+			if !reflect.DeepEqual(res, params.res) {
+				t.Fatalf("should have %v result: got %v", params.res, res)
+			}
+			if err != nil && params.err != nil && err.Error() != params.err.Error() {
+				t.Fatalf("should have '%v' error: got '%v'", params.err, err)
+			}
+			if (err == nil || params.err == nil) && err != params.err {
+				t.Fatalf("should have %v error: got %v", params.err, err)
+			}
+
+		})
+	}
+}
+func TestIsolatedCPUs(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+	isolated, err := fs.IsolatedCPUs()
+	expected := []uint16{1, 2, 3, 4, 5, 6, 7, 9}
+	if !reflect.DeepEqual(isolated, expected) {
+		t.Errorf("Result not correct: want %v, have %v", expected, isolated)
+	}
+	if err != nil {
+		t.Errorf("Error not correct: want %v, have %v", nil, err)
+	}
+}