Repository: prometheus/procfs
Pull request #254 (2020-01-07 18:37:51)

Added parsing netfilter conntrack statistics from net/stat/nf_conntrack
Added parsing of conntrack statistics from procfs for this PR in node_exporter project: https://github.com/prometheus/node_exporter/pull/1155

@discordianfish please review

pgier: Fixes #217 
pgier: Thanks for the update, looks like the new commit doesn't have a signoff, you can either squash it into the first commit, or do a `git commit --amend --signoff`

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index a21ccad..e4c17a2 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -1527,11 +1527,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
@@ -1833,18 +1828,18 @@ Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp
 Lines: 4
-  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
-   0: 0A000005:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
-   1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
-   2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
+  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
+   0: 00000000:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
+   1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
+   2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp6
 Lines: 3
   sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops
  1315: 00000000000000000000000000000000:14EB 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000   981        0 21040 2 0000000013726323 0
- 6073: 000080FE00000000FFADE15609667CFE:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000  1000        0 11337031 2 00000000b9256fdd 0
-Mode: 644
+ 6073: 00000000000000000000000000000000:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000  1000        0 11337031 2 00000000b9256fdd 0
+Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp_broken
 Lines: 2
@@ -1957,12 +1952,6 @@ procs_blocked 1
 softirq 5057579 250191 1481983 1647 211099 186066 0 1783454 622196 12499 508444
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/swaps
-Lines: 2
-Filename				Type		Size	Used	Priority
-/dev/dm-2                               partition	131068	176	-2
-Mode: 444
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/symlinktargets
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/loadavg.go b/loadavg.go
deleted file mode 100644
index 00bbe14..0000000
--- a/loadavg.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2019 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procfs
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
-)
-
-// LoadAvg represents an entry in /proc/loadavg
-type LoadAvg struct {
-	Load1  float64
-	Load5  float64
-	Load15 float64
-}
-
-// LoadAvg returns loadavg from /proc.
-func (fs FS) LoadAvg() (*LoadAvg, error) {
-	path := fs.proc.Path("loadavg")
-
-	data, err := util.ReadFileNoStat(path)
-	if err != nil {
-		return nil, err
-	}
-	return parseLoad(data)
-}
-
-// Parse /proc loadavg and return 1m, 5m and 15m.
-func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
-	loads := make([]float64, 3)
-	parts := strings.Fields(string(loadavgBytes))
-	if len(parts) < 3 {
-		return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %s", string(loadavgBytes))
-	}
-
-	var err error
-	for i, load := range parts[0:3] {
-		loads[i], err = strconv.ParseFloat(load, 64)
-		if err != nil {
-			return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
-		}
-	}
-	return &LoadAvg{
-		Load1:  loads[0],
-		Load5:  loads[1],
-		Load15: loads[2],
-	}, nil
-}
diff --git a/net_conntrackstat.go b/net_conntrackstat.go
new file mode 100644
index 0000000..1e27c83
--- /dev/null
+++ b/net_conntrackstat.go
@@ -0,0 +1,153 @@
+// Copyright 2020 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// A ConntrackStatEntry represents one line from net/stat/nf_conntrack
+// and contains netfilter conntrack statistics at one CPU core
+type ConntrackStatEntry struct {
+	Entries       uint64
+	Found         uint64
+	Invalid       uint64
+	Ignore        uint64
+	Insert        uint64
+	InsertFailed  uint64
+	Drop          uint64
+	EarlyDrop     uint64
+	SearchRestart uint64
+}
+
+// Retrieves netfilter's conntrack statistics, split by CPU cores
+func (fs FS) ConntrackStat() ([]ConntrackStatEntry, error) {
+	return readConntrackStat(fs.proc.Path("net", "stat", "nf_conntrack"))
+}
+
+// Parses a slice of ConntrackStatEntries from the given filepath
+func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
+	// This file is small and can be read with one syscall.
+	b, err := util.ReadFileNoStat(path)
+	if err != nil {
+		// Do not wrap this error so the caller can detect os.IsNotExist and
+		// similar conditions.
+		return nil, err
+	}
+
+	stat, err := parseConntrackStat(bytes.NewReader(b))
+	if err != nil {
+		return nil, fmt.Errorf("failed to read conntrack stats from %q: %v", path, err)
+	}
+
+	return stat, nil
+}
+
+// Reads the contents of a conntrack statistics file and parses a slice of ConntrackStatEntries
+func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
+	var entries []ConntrackStatEntry
+
+	scanner := bufio.NewScanner(r)
+	scanner.Scan()
+	for scanner.Scan() {
+		fields := strings.Fields(scanner.Text())
+		conntrackEntry, err := parseConntrackStatEntry(fields)
+		if err != nil {
+			return nil, err
+		}
+		entries = append(entries, *conntrackEntry)
+	}
+
+	return entries, nil
+}
+
+// Parses a ConntrackStatEntry from given array of fields
+func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
+	if len(fields) != 17 {
+		return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
+	}
+	entry := &ConntrackStatEntry{}
+
+	entries, err := parseConntrackStatField(fields[0])
+	if err != nil {
+		return nil, err
+	}
+	entry.Entries = entries
+
+	found, err := parseConntrackStatField(fields[2])
+	if err != nil {
+		return nil, err
+	}
+	entry.Found = found
+
+	invalid, err := parseConntrackStatField(fields[4])
+	if err != nil {
+		return nil, err
+	}
+	entry.Invalid = invalid
+
+	ignore, err := parseConntrackStatField(fields[5])
+	if err != nil {
+		return nil, err
+	}
+	entry.Ignore = ignore
+
+	insert, err := parseConntrackStatField(fields[8])
+	if err != nil {
+		return nil, err
+	}
+	entry.Insert = insert
+
+	insertFailed, err := parseConntrackStatField(fields[9])
+	if err != nil {
+		return nil, err
+	}
+	entry.InsertFailed = insertFailed
+
+	drop, err := parseConntrackStatField(fields[10])
+	if err != nil {
+		return nil, err
+	}
+	entry.Drop = drop
+
+	earlyDrop, err := parseConntrackStatField(fields[11])
+	if err != nil {
+		return nil, err
+	}
+	entry.EarlyDrop = earlyDrop
+
+	searchRestart, err := parseConntrackStatField(fields[16])
+	if err != nil {
+		return nil, err
+	}
+	entry.SearchRestart = searchRestart
+
+	return entry, nil
+}
+
+// Parses a uint64 from given hex in string
+func parseConntrackStatField(field string) (uint64, error) {
+	val, err := strconv.ParseUint(field, 16, 64)
+	if err != nil {
+		return 0, fmt.Errorf("couldn't parse \"%s\" field: %s", field, err)
+	}
+	return val, err
+}
diff --git a/net_softnet.go b/net_softnet.go
index 2a65cf1..6fcad20 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -14,85 +14,78 @@
 package procfs
 
 import (
-	"bufio"
-	"bytes"
 	"fmt"
-	"io"
+	"io/ioutil"
 	"strconv"
 	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
 )
 
 // For the proc file format details,
 // see 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
-type SoftnetStat struct {
+// SoftnetEntry contains a single row of data from /proc/net/softnet_stat
+type SoftnetEntry struct {
 	// Number of processed packets
-	Processed uint32
+	Processed uint
 	// Number of dropped packets
-	Dropped uint32
+	Dropped uint
 	// Number of times processing packets ran out of quota
-	TimeSqueezed uint32
+	TimeSqueezed uint
 }
 
-// NetSoftnetStat reads data from /proc/net/softnet_stat.
-func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
-	b, err := util.ReadFileNoStat(fs.proc.Path("net/softnet_stat"))
+// GatherSoftnetStats reads /proc/net/softnet_stat, parse the relevant columns,
+// and then return a slice of SoftnetEntry's.
+func (fs FS) GatherSoftnetStats() ([]SoftnetEntry, error) {
+	data, err := ioutil.ReadFile(fs.proc.Path("net/softnet_stat"))
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error reading softnet %s: %s", fs.proc.Path("net/softnet_stat"), err)
 	}
 
-	entries, err := parseSoftnet(bytes.NewReader(b))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse /proc/net/softnet_stat: %v", err)
-	}
-
-	return entries, nil
+	return parseSoftnetEntries(data)
 }
 
-func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
-	const expectedColumns = 11
-
-	s := bufio.NewScanner(r)
-
-	var stats []SoftnetStat
-	for s.Scan() {
-		columns := strings.Fields(s.Text())
+func parseSoftnetEntries(data []byte) ([]SoftnetEntry, error) {
+	lines := strings.Split(string(data), "\n")
+	entries := make([]SoftnetEntry, 0)
+	var err error
+	const (
+		expectedColumns = 11
+	)
+	for _, line := range lines {
+		columns := strings.Fields(line)
 		width := len(columns)
-
-		if width != 11 {
-			return nil, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
+		if width == 0 {
+			continue
 		}
-
-		// We only parse the first three columns at the moment.
-		us, err := parseHexUint32s(columns[0:3])
-		if err != nil {
-			return nil, err
+		if width != expectedColumns {
+			return []SoftnetEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
 		}
-
-		stats = append(stats, SoftnetStat{
-			Processed:    us[0],
-			Dropped:      us[1],
-			TimeSqueezed: us[2],
-		})
+		var entry SoftnetEntry
+		if entry, err = parseSoftnetEntry(columns); err != nil {
+			return []SoftnetEntry{}, err
+		}
+		entries = append(entries, entry)
 	}
 
-	return stats, nil
+	return entries, nil
 }
 
-func parseHexUint32s(ss []string) ([]uint32, error) {
-	us := make([]uint32, 0, len(ss))
-	for _, s := range ss {
-		u, err := strconv.ParseUint(s, 16, 32)
-		if err != nil {
-			return nil, err
-		}
-
-		us = append(us, uint32(u))
+func parseSoftnetEntry(columns []string) (SoftnetEntry, error) {
+	var err error
+	var processed, dropped, timeSqueezed uint64
+	if processed, err = strconv.ParseUint(columns[0], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 0: %s", err)
 	}
-
-	return us, nil
+	if dropped, err = strconv.ParseUint(columns[1], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 1: %s", err)
+	}
+	if timeSqueezed, err = strconv.ParseUint(columns[2], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 2: %s", err)
+	}
+	return SoftnetEntry{
+		Processed:    uint(processed),
+		Dropped:      uint(dropped),
+		TimeSqueezed: uint(timeSqueezed),
+	}, nil
 }
diff --git a/net_udp.go b/net_udp.go
index d017e3f..1799119 100644
--- a/net_udp.go
+++ b/net_udp.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Prometheus Authors
+// Copyright 2019 The Prometheus Authors
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -15,10 +15,8 @@ package procfs
 
 import (
 	"bufio"
-	"encoding/hex"
 	"fmt"
 	"io"
-	"net"
 	"os"
 	"strconv"
 	"strings"
@@ -56,9 +54,9 @@ type (
 	// For the proc file format details, see https://linux.die.net/man/5/proc.
 	netUDPLine struct {
 		Sl        uint64
-		LocalAddr net.IP
+		LocalAddr uint64
 		LocalPort uint64
-		RemAddr   net.IP
+		RemAddr   uint64
 		RemPort   uint64
 		St        uint64
 		TxQueue   uint64
@@ -174,7 +172,7 @@ func parseNetUDPLine(fields []string) (*netUDPLine, error) {
 		return nil, fmt.Errorf(
 			"cannot parse local_address field in udp socket line: %s", fields[1])
 	}
-	if line.LocalAddr, err = hex.DecodeString(l[0]); err != nil {
+	if line.LocalAddr, err = strconv.ParseUint(l[0], 16, 64); err != nil {
 		return nil, fmt.Errorf(
 			"cannot parse local_address value in udp socket line: %s", err)
 	}
@@ -189,7 +187,7 @@ func parseNetUDPLine(fields []string) (*netUDPLine, error) {
 		return nil, fmt.Errorf(
 			"cannot parse rem_address field in udp socket line: %s", fields[1])
 	}
-	if line.RemAddr, err = hex.DecodeString(r[0]); err != nil {
+	if line.RemAddr, err = strconv.ParseUint(r[0], 16, 64); err != nil {
 		return nil, fmt.Errorf(
 			"cannot parse rem_address value in udp socket line: %s", err)
 	}
diff --git a/swaps.go b/swaps.go
deleted file mode 100644
index 15edc22..0000000
--- a/swaps.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2019 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procfs
-
-import (
-	"bufio"
-	"bytes"
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
-)
-
-// Swap represents an entry in /proc/swaps.
-type Swap struct {
-	Filename string
-	Type     string
-	Size     int
-	Used     int
-	Priority int
-}
-
-// Swaps returns a slice of all configured swap devices on the system.
-func (fs FS) Swaps() ([]*Swap, error) {
-	data, err := util.ReadFileNoStat(fs.proc.Path("swaps"))
-	if err != nil {
-		return nil, err
-	}
-	return parseSwaps(data)
-}
-
-func parseSwaps(info []byte) ([]*Swap, error) {
-	swaps := []*Swap{}
-	scanner := bufio.NewScanner(bytes.NewReader(info))
-	scanner.Scan() // ignore header line
-	for scanner.Scan() {
-		swapString := scanner.Text()
-		parsedSwap, err := parseSwapString(swapString)
-		if err != nil {
-			return nil, err
-		}
-		swaps = append(swaps, parsedSwap)
-	}
-
-	err := scanner.Err()
-	return swaps, err
-}
-
-func parseSwapString(swapString string) (*Swap, error) {
-	var err error
-
-	swapFields := strings.Fields(swapString)
-	swapLength := len(swapFields)
-	if swapLength < 5 {
-		return nil, fmt.Errorf("too few fields in swap string: %s", swapString)
-	}
-
-	swap := &Swap{
-		Filename: swapFields[0],
-		Type:     swapFields[1],
-	}
-
-	swap.Size, err = strconv.Atoi(swapFields[2])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap size: %s", swapFields[2])
-	}
-	swap.Used, err = strconv.Atoi(swapFields[3])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap used: %s", swapFields[3])
-	}
-	swap.Priority, err = strconv.Atoi(swapFields[4])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap priority: %s", swapFields[4])
-	}
-
-	return swap, nil
-}


Test patch:
diff --git a/loadavg_test.go b/loadavg_test.go
deleted file mode 100644
index 924ba0c..0000000
--- a/loadavg_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2019 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procfs
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestLoadAvg(t *testing.T) {
-	fs, err := NewFS(procTestFixtures)
-	if err != nil {
-		t.Fatalf("failed to open procfs: %v", err)
-	}
-
-	loadavg, err := fs.LoadAvg()
-	if err != nil {
-		t.Fatalf("failed to get loadavg: %v", err)
-	}
-
-	if diff := cmp.Diff(0.02, loadavg.Load1); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per a minute:\n%s", diff)
-	}
-	if diff := cmp.Diff(0.04, loadavg.Load5); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per five minutes:\n%s", diff)
-	}
-	if diff := cmp.Diff(0.05, loadavg.Load15); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per fifteen minutes:\n%s", diff)
-	}
-}
-
-func Test_parseLoad(t *testing.T) {
-	tests := []struct {
-		name    string
-		s       string
-		ok      bool
-		loadavg *LoadAvg
-	}{
-		{
-			name: "empty",
-			ok:   false,
-		},
-		{
-			name: "not enough fields",
-			s:    `0.00 0.03`,
-			ok:   false,
-		},
-		{
-			name: "invalid line",
-			s:    `malformed line`,
-			ok:   false,
-		},
-		{
-			name:    "valid line",
-			s:       `0.00 0.03 0.05 1/502 33634`,
-			ok:      true,
-			loadavg: &LoadAvg{Load1: 0, Load5: 0.03, Load15: 0.05},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			loadavg, err := parseLoad([]byte(tt.s))
-			if err != nil {
-				if tt.ok {
-					t.Fatalf("failed to parse loadavg: %v", err)
-				}
-
-				t.Logf("OK error: %v", err)
-				return
-			}
-			if !tt.ok {
-				t.Fatal("expected an error, but none occurred")
-			}
-
-			if diff := cmp.Diff(tt.loadavg, loadavg); diff != "" {
-				t.Errorf("unexpected loadavg(-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/net_conntrackstat_test.go b/net_conntrackstat_test.go
new file mode 100644
index 0000000..18fc066
--- /dev/null
+++ b/net_conntrackstat_test.go
@@ -0,0 +1,85 @@
+// Copyright 2020 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+func TestParseConntrackStat(t *testing.T) {
+	var nfConntrackStat = []byte(`entries  searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error  expect_new expect_create expect_delete search_restart
+00000021  00000000 00000000 00000000 00000003 0000588a 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
+00000021  00000000 00000000 00000000 00000002 000056a4 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000002
+00000021  00000000 00000000 00000000 00000001 000058d4 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000001
+00000021  00000000 00000000 00000000 0000002f 00005688 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000004
+`)
+	r := bytes.NewReader(nfConntrackStat)
+
+	have, err := parseConntrackStat(r)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := []ConntrackStatEntry{
+		ConntrackStatEntry{
+			Entries:       33,
+			Found:         0,
+			Invalid:       3,
+			Ignore:        22666,
+			Insert:        0,
+			InsertFailed:  0,
+			Drop:          0,
+			EarlyDrop:     0,
+			SearchRestart: 0,
+		},
+		ConntrackStatEntry{
+			Entries:       33,
+			Found:         0,
+			Invalid:       2,
+			Ignore:        22180,
+			Insert:        0,
+			InsertFailed:  0,
+			Drop:          0,
+			EarlyDrop:     0,
+			SearchRestart: 2,
+		},
+		ConntrackStatEntry{
+			Entries:       33,
+			Found:         0,
+			Invalid:       1,
+			Ignore:        22740,
+			Insert:        0,
+			InsertFailed:  0,
+			Drop:          0,
+			EarlyDrop:     0,
+			SearchRestart: 1,
+		},
+		ConntrackStatEntry{
+			Entries:       33,
+			Found:         0,
+			Invalid:       47,
+			Ignore:        22152,
+			Insert:        0,
+			InsertFailed:  0,
+			Drop:          0,
+			EarlyDrop:     0,
+			SearchRestart: 4,
+		},
+	}
+	if !reflect.DeepEqual(want, have) {
+		t.Errorf("want %v, have %v", want, have)
+	}
+}
diff --git a/net_softnet_test.go b/net_softnet_test.go
index 5a52131..a2658a3 100644
--- a/net_softnet_test.go
+++ b/net_softnet_test.go
@@ -15,28 +15,28 @@ package procfs
 
 import (
 	"testing"
-
-	"github.com/google/go-cmp/cmp"
 )
 
-func TestNetSoftnet(t *testing.T) {
+func TestSoftnet(t *testing.T) {
 	fs, err := NewFS(procTestFixtures)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	want := []SoftnetStat{{
-		Processed:    0x00015c73,
-		Dropped:      0x00020e76,
-		TimeSqueezed: 0xf0000769,
-	}}
-
-	got, err := fs.NetSoftnetStat()
+	entries, err := fs.GatherSoftnetStats()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if diff := cmp.Diff(want, got); diff != "" {
-		t.Fatalf("unexpected softnet stats(-want +got):\n%s", diff)
+	if want, got := uint(0x00015c73), entries[0].Processed; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
+	}
+
+	if want, got := uint(0x00020e76), entries[0].Dropped; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
+	}
+
+	if want, got := uint(0xF0000769), entries[0].TimeSqueezed; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
 	}
 }
diff --git a/net_udp_test.go b/net_udp_test.go
index 363fdb2..563530c 100644
--- a/net_udp_test.go
+++ b/net_udp_test.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Prometheus Authors
+// Copyright 2019 The Prometheus Authors
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -14,7 +14,6 @@
 package procfs
 
 import (
-	"net"
 	"reflect"
 	"testing"
 )
@@ -31,9 +30,9 @@ func Test_parseNetUDPLine(t *testing.T) {
 			fields: []string{"11:", "00000000:0000", "00000000:0000", "0A", "00000017:0000002A", "0:0", "0", "1000"},
 			want: &netUDPLine{
 				Sl:        11,
-				LocalAddr: net.IP{0, 0, 0, 0},
+				LocalAddr: 0,
 				LocalPort: 0,
-				RemAddr:   net.IP{0, 0, 0, 0},
+				RemAddr:   0,
 				RemPort:   0,
 				St:        10,
 				TxQueue:   23,
@@ -100,8 +99,19 @@ func Test_parseNetUDPLine(t *testing.T) {
 			if tt.want == nil && got != nil {
 				t.Errorf("parseNetUDPLine() = %v, want %v", got, tt.want)
 			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want)
+			if got != nil {
+				if (got.Sl != tt.want.Sl) ||
+					(got.LocalAddr != tt.want.LocalAddr) ||
+					(got.LocalPort != tt.want.LocalPort) ||
+					(got.RemAddr != tt.want.RemAddr) ||
+					(got.RemPort != tt.want.RemPort) ||
+					(got.St != tt.want.St) ||
+					(got.TxQueue != tt.want.TxQueue) ||
+					(got.RxQueue != tt.want.RxQueue) ||
+					(got.UID != tt.want.UID) {
+
+					t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want)
+				}
 			}
 		})
 	}
@@ -119,37 +129,13 @@ func Test_newNetUDP(t *testing.T) {
 			file: "fixtures/proc/net/udp",
 			want: []*netUDPLine{
 				&netUDPLine{
-					Sl:        0,
-					LocalAddr: net.IP{10, 0, 0, 5},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   0,
-					RxQueue:   1,
-					UID:       0,
+					Sl: 0, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 0, RxQueue: 1, UID: 0,
 				},
 				&netUDPLine{
-					Sl:        1,
-					LocalAddr: net.IP{0, 0, 0, 0},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   1,
-					RxQueue:   0,
-					UID:       0,
+					Sl: 1, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 0, UID: 0,
 				},
 				&netUDPLine{
-					Sl:        2,
-					LocalAddr: net.IP{0, 0, 0, 0},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   1,
-					RxQueue:   1,
-					UID:       0,
+					Sl: 2, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 1, UID: 0,
 				},
 			},
 			wantErr: false,
@@ -159,26 +145,10 @@ func Test_newNetUDP(t *testing.T) {
 			file: "fixtures/proc/net/udp6",
 			want: []*netUDPLine{
 				&netUDPLine{
-					Sl:        1315,
-					LocalAddr: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					LocalPort: 5355,
-					RemAddr:   net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					RemPort:   0,
-					St:        7,
-					TxQueue:   0,
-					RxQueue:   0,
-					UID:       981,
+					Sl: 1315, LocalAddr: 0, LocalPort: 5355, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 981,
 				},
 				&netUDPLine{
-					Sl:        6073,
-					LocalAddr: net.IP{0, 0, 128, 254, 0, 0, 0, 0, 255, 173, 225, 86, 9, 102, 124, 254},
-					LocalPort: 51073,
-					RemAddr:   net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					RemPort:   0,
-					St:        7,
-					TxQueue:   0,
-					RxQueue:   0,
-					UID:       1000,
+					Sl: 6073, LocalAddr: 0, LocalPort: 51073, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 1000,
 				},
 			},
 			wantErr: false,
diff --git a/swaps_test.go b/swaps_test.go
deleted file mode 100644
index 70db6ab..0000000
--- a/swaps_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2019 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procfs
-
-import (
-	"reflect"
-	"testing"
-)
-
-func TestSwaps(t *testing.T) {
-	fs, err := NewFS(procTestFixtures)
-	if err != nil {
-		t.Fatalf("failed to open procfs: %v", err)
-	}
-
-	swaps, err := fs.Swaps()
-	if err != nil {
-		t.Fatalf("failed to get swaps: %v", err)
-	}
-
-	if len(swaps) != 1 {
-		t.Fatalf("expected 1 swap entry, got %d", len(swaps))
-	}
-	swap := swaps[0]
-
-	if swap.Filename != "/dev/dm-2" {
-		t.Errorf("expected swap.Filename /dev/dm-2, got %s", swap.Filename)
-	}
-	if swap.Type != "partition" {
-		t.Errorf("expected swap.Type partition, got %s", swap.Type)
-	}
-	if swap.Size != 131068 {
-		t.Errorf("expected swap.Size 131068, got %d", swap.Size)
-	}
-	if swap.Used != 176 {
-		t.Errorf("expected swap.Used 176, got %d", swap.Used)
-	}
-	if swap.Priority != -2 {
-		t.Errorf("expected swap.Priority -2, got %d", swap.Priority)
-	}
-}
-
-func TestParseSwapString(t *testing.T) {
-	tests := []struct {
-		name    string
-		s       string
-		swap    *Swap
-		invalid bool
-	}{
-		{
-			name:    "device-mapper volume",
-			s:       "/dev/dm-2                               partition       131068  1024    -2",
-			invalid: false,
-			swap: &Swap{
-				Filename: "/dev/dm-2",
-				Type:     "partition",
-				Size:     131068,
-				Used:     1024,
-				Priority: -2,
-			},
-		},
-		{
-			name:    "Swap file",
-			s:       "/foo                                    file            1048572 0       -3",
-			invalid: false,
-			swap: &Swap{
-				Filename: "/foo",
-				Type:     "file",
-				Size:     1048572,
-				Used:     0,
-				Priority: -3,
-			},
-		},
-		{
-			name:    "Invalid number",
-			s:       "/dev/sda2                               partition       hello   world   -2",
-			invalid: true,
-		},
-		{
-			name:    "Not enough fields",
-			s:       "/dev/dm-2                               partition       131068  1024",
-			invalid: true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			swap, err := parseSwapString(tt.s)
-
-			if tt.invalid && err == nil {
-				t.Error("unexpected success")
-			}
-			if !tt.invalid && err != nil {
-				t.Errorf("unexpected error: %v", err)
-			}
-
-			if !reflect.DeepEqual(tt.swap, swap) {
-				t.Errorf("swap:\nwant:\n%+v\nhave:\n%+v", tt.swap, swap)
-			}
-		})
-	}
-}