Repository: prometheus/procfs
Pull request #247 (2019-12-12 18:00:53)

procfs: clean up softnet parsing APIs and code
Signed-off-by: Matt Layher <mdlayher@gmail.com>

These API names and internal code are more in line with the direction we are taking the procfs code.


Patch:
diff --git a/Makefile.common b/Makefile.common
index d7aea1b..066866e 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -69,12 +69,12 @@ else
 	GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
 endif
 
-PROMU_VERSION ?= 0.4.0
+PROMU_VERSION ?= 0.5.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.16.0
+GOLANGCI_LINT_VERSION ?= v1.18.0
 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
 # windows isn't included here because of the path separator being different.
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
@@ -86,7 +86,8 @@ endif
 PREFIX                  ?= $(shell pwd)
 BIN_DIR                 ?= $(shell pwd)
 DOCKER_IMAGE_TAG        ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
-DOCKERFILE_PATH         ?= ./
+DOCKERFILE_PATH         ?= ./Dockerfile
+DOCKERBUILD_CONTEXT     ?= ./
 DOCKER_REPO             ?= prom
 
 DOCKER_ARCHS            ?= amd64
@@ -200,7 +201,7 @@ endif
 .PHONY: common-build
 common-build: promu
 	@echo ">> building binaries"
-	GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX)
+	GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
 
 .PHONY: common-tarball
 common-tarball: promu
@@ -211,9 +212,10 @@ common-tarball: promu
 common-docker: $(BUILD_DOCKER_ARCHS)
 $(BUILD_DOCKER_ARCHS): common-docker-%:
 	docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
+		-f $(DOCKERFILE_PATH) \
 		--build-arg ARCH="$*" \
 		--build-arg OS="linux" \
-		$(DOCKERFILE_PATH)
+		$(DOCKERBUILD_CONTEXT)
 
 .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
 common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
diff --git a/net_softnet.go b/net_softnet.go
index 6fcad20..2a65cf1 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -14,78 +14,85 @@
 package procfs
 
 import (
+	"bufio"
+	"bytes"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"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.
 
-// SoftnetEntry contains a single row of data from /proc/net/softnet_stat
-type SoftnetEntry struct {
+// SoftnetStat contains a single row of data from /proc/net/softnet_stat
+type SoftnetStat struct {
 	// Number of processed packets
-	Processed uint
+	Processed uint32
 	// Number of dropped packets
-	Dropped uint
+	Dropped uint32
 	// Number of times processing packets ran out of quota
-	TimeSqueezed uint
+	TimeSqueezed uint32
 }
 
-// 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"))
+// NetSoftnetStat reads data from /proc/net/softnet_stat.
+func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
+	b, err := util.ReadFileNoStat(fs.proc.Path("net/softnet_stat"))
 	if err != nil {
-		return nil, fmt.Errorf("error reading softnet %s: %s", fs.proc.Path("net/softnet_stat"), err)
+		return nil, err
 	}
 
-	return parseSoftnetEntries(data)
+	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
 }
 
-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)
+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())
 		width := len(columns)
-		if width == 0 {
-			continue
-		}
-		if width != expectedColumns {
-			return []SoftnetEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
+
+		if width != 11 {
+			return nil, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
 		}
-		var entry SoftnetEntry
-		if entry, err = parseSoftnetEntry(columns); err != nil {
-			return []SoftnetEntry{}, err
+
+		// We only parse the first three columns at the moment.
+		us, err := parseHexUint32s(columns[0:3])
+		if err != nil {
+			return nil, err
 		}
-		entries = append(entries, entry)
+
+		stats = append(stats, SoftnetStat{
+			Processed:    us[0],
+			Dropped:      us[1],
+			TimeSqueezed: us[2],
+		})
 	}
 
-	return entries, nil
+	return stats, nil
 }
 
-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)
-	}
-	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)
+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))
 	}
-	return SoftnetEntry{
-		Processed:    uint(processed),
-		Dropped:      uint(dropped),
-		TimeSqueezed: uint(timeSqueezed),
-	}, nil
+
+	return us, nil
 }


Test patch:
diff --git a/net_softnet_test.go b/net_softnet_test.go
index a2658a3..5a52131 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 TestSoftnet(t *testing.T) {
+func TestNetSoftnet(t *testing.T) {
 	fs, err := NewFS(procTestFixtures)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	entries, err := fs.GatherSoftnetStats()
+	want := []SoftnetStat{{
+		Processed:    0x00015c73,
+		Dropped:      0x00020e76,
+		TimeSqueezed: 0xf0000769,
+	}}
+
+	got, err := fs.NetSoftnetStat()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	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)
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Fatalf("unexpected softnet stats(-want +got):\n%s", diff)
 	}
 }