Repository: prometheus/procfs
Pull request #271 (2020-02-28 10:32:05)

Fix softnet parsing
Allow 9 or more columns from softnet, older kernels had fewer columns.

Fix for https://github.com/prometheus/node_exporter/issues/1625

Signed-off-by: Ben Kochie <superq@gmail.com>

SuperQ: I can add a variation to the fixture.
SuperQ: Updated with additional tests.
SuperQ: @mdlayher Any other comments?

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index 200d9ff..45a7321 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -1551,11 +1551,6 @@ blocksize    : 16
 min keysize  : 16
 max keysize  : 32
 
-Mode: 444
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/loadavg
-Lines: 1
-0.02 0.04 0.05 1/497 11947
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/diskstats
@@ -1644,6 +1639,11 @@ xpc 399724544 92823103 86219234
 debug 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/loadavg
+Lines: 1
+0.02 0.04 0.05 1/497 11947
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/mdstat
 Lines: 56
 Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
@@ -1851,8 +1851,14 @@ FRAG6: inuse 0 memory 0
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/softnet_stat
-Lines: 1
+Lines: 2
 00015c73 00020e76 F0000769 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+01663fb2 00000000 000109a4 00000000 00000000 00000000 00000000 00000000 00000000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/net/softnet_stat.broken
+Lines: 1
+00015c73 00020e76 F0000769 00000000
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp
diff --git a/net_softnet.go b/net_softnet.go
index 2a65cf1..db5debd 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -25,7 +25,9 @@ import (
 )
 
 // For the proc file format details,
-// see https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
+// See:
+// * Linux 2.6.23 https://elixir.bootlin.com/linux/v2.6.23/source/net/core/dev.c#L2343
+// * Linux 4.17 https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
 // and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810.
 
 // SoftnetStat contains a single row of data from /proc/net/softnet_stat
@@ -38,9 +40,11 @@ type SoftnetStat struct {
 	TimeSqueezed uint32
 }
 
+var softNetProcFile = "net/softnet_stat"
+
 // NetSoftnetStat reads data from /proc/net/softnet_stat.
 func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
-	b, err := util.ReadFileNoStat(fs.proc.Path("net/softnet_stat"))
+	b, err := util.ReadFileNoStat(fs.proc.Path(softNetProcFile))
 	if err != nil {
 		return nil, err
 	}
@@ -54,7 +58,7 @@ func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
 }
 
 func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
-	const expectedColumns = 11
+	const minColumns = 9
 
 	s := bufio.NewScanner(r)
 
@@ -63,8 +67,8 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
 		columns := strings.Fields(s.Text())
 		width := len(columns)
 
-		if width != 11 {
-			return nil, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
+		if width < minColumns {
+			return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns)
 		}
 
 		// We only parse the first three columns at the moment.


Test patch:
diff --git a/net_softnet_test.go b/net_softnet_test.go
index 5a52131..f25e09f 100644
--- a/net_softnet_test.go
+++ b/net_softnet_test.go
@@ -29,7 +29,11 @@ func TestNetSoftnet(t *testing.T) {
 		Processed:    0x00015c73,
 		Dropped:      0x00020e76,
 		TimeSqueezed: 0xf0000769,
-	}}
+	},
+		{
+			Processed:    0x01663fb2,
+			TimeSqueezed: 0x0109a4,
+		}}
 
 	got, err := fs.NetSoftnetStat()
 	if err != nil {
@@ -40,3 +44,16 @@ func TestNetSoftnet(t *testing.T) {
 		t.Fatalf("unexpected softnet stats(-want +got):\n%s", diff)
 	}
 }
+
+func TestBadSoftnet(t *testing.T) {
+	softNetProcFile = "net/softnet_stat.broken"
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = fs.NetSoftnetStat()
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+}