Repository: prometheus/procfs
Pull request #619 (2024-03-22 11:14:02)

bugfix: Allow multiple `xprt` fields for NFS stats
Allow parsing multiple `xprt` fields present in the same NFS transport stats data.

Fixes: #450

***

NOTE: This is a breaking change.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index d882217..018c8e1 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,7 +4,7 @@ version: 2.1
 jobs:
   lint:
     docker:
-    - image: cimg/go:1.21
+    - image: cimg/go:1.22
     steps:
     - checkout
     - run: make check_license
@@ -46,9 +46,8 @@ workflows:
         matrix:
           parameters:
             go_version:
-            - "1.19"
-            - "1.20"
             - "1.21"
+            - "1.22"
     - test:
         name: test-windows
         os: windows
@@ -56,6 +55,5 @@ workflows:
         matrix:
           parameters:
             go_version:
-            - "1.19"
-            - "1.20"
             - "1.21"
+            - "1.22"
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 5670d60..b36f71c 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -24,7 +24,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
       - name: install Go
         uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
         with:
@@ -33,6 +33,6 @@ jobs:
         run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
         if: github.repository == 'prometheus/snmp_exporter'
       - name: Lint
-        uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0
+        uses: golangci/golangci-lint-action@9d1e0624a798bb64f6c3cea93db47765312263dc # v5.1.0
         with:
           version: v1.56.2
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 56ba67d..e00f3b3 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -1,2 +1,3 @@
 * Johannes 'fish' Ziemke <github@freigeist.org> @discordianfish
-* Paul Gier <pgier@redhat.com> @pgier
+* Paul Gier <paulgier@gmail.com> @pgier
+* Ben Kochie <superq@gmail.com> @SuperQ
diff --git a/Makefile.common b/Makefile.common
index 483b3bf..0e9ace2 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -49,13 +49,13 @@ endif
 GOTEST := $(GO) test
 GOTEST_DIR :=
 ifneq ($(CIRCLE_JOB),)
-ifneq ($(shell command -v gotestsum > /dev/null),)
+ifneq ($(shell command -v gotestsum 2> /dev/null),)
 	GOTEST_DIR := test-results
 	GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
 endif
 endif
 
-PROMU_VERSION ?= 0.15.0
+PROMU_VERSION ?= 0.17.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 SKIP_GOLANGCI_LINT :=
@@ -182,7 +182,7 @@ endif
 .PHONY: common-yamllint
 common-yamllint:
 	@echo ">> running yamllint on all YAML files in the repository"
-ifeq (, $(shell command -v yamllint > /dev/null))
+ifeq (, $(shell command -v yamllint 2> /dev/null))
 	@echo "yamllint not installed so skipping"
 else
 	yamllint .
diff --git a/arp.go b/arp.go
index 28783e2..cdcc8a7 100644
--- a/arp.go
+++ b/arp.go
@@ -55,7 +55,7 @@ type ARPEntry struct {
 func (fs FS) GatherARPEntries() ([]ARPEntry, error) {
 	data, err := os.ReadFile(fs.proc.Path("net/arp"))
 	if err != nil {
-		return nil, fmt.Errorf("%s: error reading arp %s: %w", ErrFileRead, fs.proc.Path("net/arp"), err)
+		return nil, fmt.Errorf("%w: error reading arp %s: %w", ErrFileRead, fs.proc.Path("net/arp"), err)
 	}
 
 	return parseARPEntries(data)
@@ -78,11 +78,11 @@ func parseARPEntries(data []byte) ([]ARPEntry, error) {
 		} else if width == expectedDataWidth {
 			entry, err := parseARPEntry(columns)
 			if err != nil {
-				return []ARPEntry{}, fmt.Errorf("%s: Failed to parse ARP entry: %v: %w", ErrFileParse, entry, err)
+				return []ARPEntry{}, fmt.Errorf("%w: Failed to parse ARP entry: %v: %w", ErrFileParse, entry, err)
 			}
 			entries = append(entries, entry)
 		} else {
-			return []ARPEntry{}, fmt.Errorf("%s: %d columns found, but expected %d: %w", ErrFileParse, width, expectedDataWidth, err)
+			return []ARPEntry{}, fmt.Errorf("%w: %d columns found, but expected %d: %w", ErrFileParse, width, expectedDataWidth, err)
 		}
 
 	}
diff --git a/buddyinfo.go b/buddyinfo.go
index 4a17363..eb88d78 100644
--- a/buddyinfo.go
+++ b/buddyinfo.go
@@ -74,7 +74,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
 		for i := 0; i < arraySize; i++ {
 			sizes[i], err = strconv.ParseFloat(parts[i+4], 64)
 			if err != nil {
-				return nil, fmt.Errorf("%s: Invalid valid in buddyinfo: %f: %w", ErrFileParse, sizes[i], err)
+				return nil, fmt.Errorf("%w: Invalid valid in buddyinfo: %f: %w", ErrFileParse, sizes[i], err)
 			}
 		}
 
diff --git a/cpuinfo.go b/cpuinfo.go
index f4f5501..f0950bb 100644
--- a/cpuinfo.go
+++ b/cpuinfo.go
@@ -194,7 +194,7 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
 	firstLine := firstNonEmptyLine(scanner)
 	match, err := regexp.MatchString("^[Pp]rocessor", firstLine)
 	if !match || !strings.Contains(firstLine, ":") {
-		return nil, fmt.Errorf("%s: Cannot parse line: %q: %w", ErrFileParse, firstLine, err)
+		return nil, fmt.Errorf("%w: Cannot parse line: %q: %w", ErrFileParse, firstLine, err)
 
 	}
 	field := strings.SplitN(firstLine, ": ", 2)
@@ -386,7 +386,7 @@ func parseCPUInfoLoong(info []byte) ([]CPUInfo, error) {
 	// find the first "processor" line
 	firstLine := firstNonEmptyLine(scanner)
 	if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
-		return nil, errors.New("invalid cpuinfo file: " + firstLine)
+		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
 	}
 	field := strings.SplitN(firstLine, ": ", 2)
 	cpuinfo := []CPUInfo{}
diff --git a/crypto.go b/crypto.go
index 13ee66f..5f2a37a 100644
--- a/crypto.go
+++ b/crypto.go
@@ -55,13 +55,13 @@ func (fs FS) Crypto() ([]Crypto, error) {
 	path := fs.proc.Path("crypto")
 	b, err := util.ReadFileNoStat(path)
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot read file %v: %w", ErrFileRead, b, err)
+		return nil, fmt.Errorf("%w: Cannot read file %v: %w", ErrFileRead, b, err)
 
 	}
 
 	crypto, err := parseCrypto(bytes.NewReader(b))
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, crypto, err)
+		return nil, fmt.Errorf("%w: Cannot parse %v: %w", ErrFileParse, crypto, err)
 	}
 
 	return crypto, nil
diff --git a/fscache.go b/fscache.go
index f560a8d..cf2e3ea 100644
--- a/fscache.go
+++ b/fscache.go
@@ -236,7 +236,7 @@ func (fs FS) Fscacheinfo() (Fscacheinfo, error) {
 
 	m, err := parseFscacheinfo(bytes.NewReader(b))
 	if err != nil {
-		return Fscacheinfo{}, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, m, err)
+		return Fscacheinfo{}, fmt.Errorf("%w: Cannot parse %v: %w", ErrFileParse, m, err)
 	}
 
 	return *m, nil
@@ -245,7 +245,7 @@ func (fs FS) Fscacheinfo() (Fscacheinfo, error) {
 func setFSCacheFields(fields []string, setFields ...*uint64) error {
 	var err error
 	if len(fields) < len(setFields) {
-		return fmt.Errorf("%s: Expected %d, but got %d: %w", ErrFileParse, len(setFields), len(fields), err)
+		return fmt.Errorf("%w: Expected %d, but got %d: %w", ErrFileParse, len(setFields), len(fields), err)
 	}
 
 	for i := range setFields {
diff --git a/go.mod b/go.mod
index 02a379e..e45aa57 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,9 @@
 module github.com/prometheus/procfs
 
-go 1.19
+go 1.21
 
 require (
 	github.com/google/go-cmp v0.6.0
-	golang.org/x/sync v0.6.0
-	golang.org/x/sys v0.18.0
+	golang.org/x/sync v0.7.0
+	golang.org/x/sys v0.19.0
 )
diff --git a/go.sum b/go.sum
index 037f53a..2e56220 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,6 @@
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/ipvs.go b/ipvs.go
index 5a145bb..bc3a20c 100644
--- a/ipvs.go
+++ b/ipvs.go
@@ -221,16 +221,16 @@ func parseIPPort(s string) (net.IP, uint16, error) {
 	case 46:
 		ip = net.ParseIP(s[1:40])
 		if ip == nil {
-			return nil, 0, fmt.Errorf("%s: Invalid IPv6 addr %s: %w", ErrFileParse, s[1:40], err)
+			return nil, 0, fmt.Errorf("%w: Invalid IPv6 addr %s: %w", ErrFileParse, s[1:40], err)
 		}
 	default:
-		return nil, 0, fmt.Errorf("%s: Unexpected IP:Port %s: %w", ErrFileParse, s, err)
+		return nil, 0, fmt.Errorf("%w: Unexpected IP:Port %s: %w", ErrFileParse, s, err)
 	}
 
 	portString := s[len(s)-4:]
 	if len(portString) != 4 {
 		return nil, 0,
-			fmt.Errorf("%s: Unexpected port string format %s: %w", ErrFileParse, portString, err)
+			fmt.Errorf("%w: Unexpected port string format %s: %w", ErrFileParse, portString, err)
 	}
 	port, err := strconv.ParseUint(portString, 16, 16)
 	if err != nil {
diff --git a/loadavg.go b/loadavg.go
index 59465c5..332e76c 100644
--- a/loadavg.go
+++ b/loadavg.go
@@ -51,7 +51,7 @@ func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
 	for i, load := range parts[0:3] {
 		loads[i], err = strconv.ParseFloat(load, 64)
 		if err != nil {
-			return nil, fmt.Errorf("%s: Cannot parse load: %f: %w", ErrFileParse, loads[i], err)
+			return nil, fmt.Errorf("%w: Cannot parse load: %f: %w", ErrFileParse, loads[i], err)
 		}
 	}
 	return &LoadAvg{
diff --git a/mdstat.go b/mdstat.go
index fdd4b95..dd2b898 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -70,7 +70,7 @@ func (fs FS) MDStat() ([]MDStat, error) {
 	}
 	mdstat, err := parseMDStat(data)
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, fs.proc.Path("mdstat"), err)
+		return nil, fmt.Errorf("%w: Cannot parse %v: %w", ErrFileParse, fs.proc.Path("mdstat"), err)
 	}
 	return mdstat, nil
 }
@@ -90,7 +90,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 
 		deviceFields := strings.Fields(line)
 		if len(deviceFields) < 3 {
-			return nil, fmt.Errorf("%s: Expected 3+ lines, got %q", ErrFileParse, line)
+			return nil, fmt.Errorf("%w: Expected 3+ lines, got %q", ErrFileParse, line)
 		}
 		mdName := deviceFields[0] // mdx
 		state := deviceFields[2]  // active or inactive
@@ -105,7 +105,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 		active, total, down, size, err := evalStatusLine(lines[i], lines[i+1])
 
 		if err != nil {
-			return nil, fmt.Errorf("%s: Cannot parse md device lines: %v: %w", ErrFileParse, active, err)
+			return nil, fmt.Errorf("%w: Cannot parse md device lines: %v: %w", ErrFileParse, active, err)
 		}
 
 		syncLineIdx := i + 2
@@ -140,7 +140,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			} else {
 				syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
 				if err != nil {
-					return nil, fmt.Errorf("%s: Cannot parse sync line in md device: %q: %w", ErrFileParse, mdName, err)
+					return nil, fmt.Errorf("%w: Cannot parse sync line in md device: %q: %w", ErrFileParse, mdName, err)
 				}
 			}
 		}
@@ -168,13 +168,13 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) {
 	statusFields := strings.Fields(statusLine)
 	if len(statusFields) < 1 {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
 	}
 
 	sizeStr := statusFields[0]
 	size, err = strconv.ParseInt(sizeStr, 10, 64)
 	if err != nil {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
 	}
 
 	if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
@@ -189,17 +189,17 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, down, size in
 
 	matches := statusLineRE.FindStringSubmatch(statusLine)
 	if len(matches) != 5 {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Could not fild all substring matches %s: %w", ErrFileParse, statusLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Could not fild all substring matches %s: %w", ErrFileParse, statusLine, err)
 	}
 
 	total, err = strconv.ParseInt(matches[2], 10, 64)
 	if err != nil {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
 	}
 
 	active, err = strconv.ParseInt(matches[3], 10, 64)
 	if err != nil {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected active %d: %w", ErrFileParse, active, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected active %d: %w", ErrFileParse, active, err)
 	}
 	down = int64(strings.Count(matches[4], "_"))
 
@@ -209,12 +209,12 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, down, size in
 func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
 	matches := recoveryLineBlocksRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected recoveryLine %s: %w", ErrFileParse, recoveryLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine %s: %w", ErrFileParse, recoveryLine, err)
 	}
 
 	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
 	if err != nil {
-		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected parsing of recoveryLine %q: %w", ErrFileParse, recoveryLine, err)
+		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected parsing of recoveryLine %q: %w", ErrFileParse, recoveryLine, err)
 	}
 
 	// Get percentage complete
@@ -244,7 +244,7 @@ func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, fin
 	}
 	speed, err = strconv.ParseFloat(matches[1], 64)
 	if err != nil {
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("%s: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
+		return syncedBlocks, pct, finish, 0, fmt.Errorf("%w: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
 	}
 
 	return syncedBlocks, pct, finish, speed, nil
diff --git a/meminfo.go b/meminfo.go
index 73a03e8..4b2c405 100644
--- a/meminfo.go
+++ b/meminfo.go
@@ -202,7 +202,7 @@ func (fs FS) Meminfo() (Meminfo, error) {
 
 	m, err := parseMemInfo(bytes.NewReader(b))
 	if err != nil {
-		return Meminfo{}, fmt.Errorf("%s: %w", ErrFileParse, err)
+		return Meminfo{}, fmt.Errorf("%w: %w", ErrFileParse, err)
 	}
 
 	return *m, nil
diff --git a/mountinfo.go b/mountinfo.go
index 388ebf3..a704c5e 100644
--- a/mountinfo.go
+++ b/mountinfo.go
@@ -109,7 +109,7 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
 	if mountInfo[6] != "" {
 		mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4])
 		if err != nil {
-			return nil, fmt.Errorf("%s: %w", ErrFileParse, err)
+			return nil, fmt.Errorf("%w: %w", ErrFileParse, err)
 		}
 	}
 	return mount, nil
diff --git a/mountstats.go b/mountstats.go
index 9d8af6d..75a3b6c 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -88,7 +88,7 @@ type MountStatsNFS struct {
 	// Statistics broken down by filesystem operation.
 	Operations []NFSOperationStats
 	// Statistics about the NFS RPC transport.
-	Transport NFSTransportStats
+	Transport []NFSTransportStats
 }
 
 // mountStats implements MountStats.
@@ -194,8 +194,6 @@ type NFSOperationStats struct {
 	CumulativeTotalResponseMilliseconds uint64
 	// Duration from when a request was enqueued to when it was completely handled.
 	CumulativeTotalRequestMilliseconds uint64
-	// The average time from the point the client sends RPC requests until it receives the response.
-	AverageRTTMilliseconds float64
 	// The count of operations that complete with tk_status < 0.  These statuses usually indicate error conditions.
 	Errors uint64
 }
@@ -434,7 +432,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 				return nil, err
 			}
 
-			stats.Transport = *tstats
+			stats.Transport = append(stats.Transport, *tstats)
 		}
 
 		// When encountering "per-operation statistics", we must break this
@@ -582,9 +580,6 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
 			CumulativeTotalResponseMilliseconds: ns[6],
 			CumulativeTotalRequestMilliseconds:  ns[7],
 		}
-		if ns[0] != 0 {
-			opStats.AverageRTTMilliseconds = float64(ns[6]) / float64(ns[0])
-		}
 
 		if len(ns) > 8 {
 			opStats.Errors = ns[8]
@@ -632,7 +627,7 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
 			return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v, protocol: %v", ErrFileParse, ss, protocol)
 		}
 	default:
-		return nil, fmt.Errorf("%s: Unrecognized NFS transport stats version: %q, protocol: %v", ErrFileParse, statVersion, protocol)
+		return nil, fmt.Errorf("%w: Unrecognized NFS transport stats version: %q, protocol: %v", ErrFileParse, statVersion, protocol)
 	}
 
 	// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
diff --git a/net_conntrackstat.go b/net_conntrackstat.go
index fdfa456..316df5f 100644
--- a/net_conntrackstat.go
+++ b/net_conntrackstat.go
@@ -58,7 +58,7 @@ func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
 
 	stat, err := parseConntrackStat(bytes.NewReader(b))
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, path, err)
+		return nil, fmt.Errorf("%w: Cannot read file: %v: %w", ErrFileRead, path, err)
 	}
 
 	return stat, nil
@@ -86,7 +86,7 @@ func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
 func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
 	entries, err := util.ParseHexUint64s(fields)
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse entry: %d: %w", ErrFileParse, entries, err)
+		return nil, fmt.Errorf("%w: Cannot parse entry: %d: %w", ErrFileParse, entries, err)
 	}
 	numEntries := len(entries)
 	if numEntries < 16 || numEntries > 17 {
diff --git a/net_ip_socket.go b/net_ip_socket.go
index ba7d9ca..b70f1fc 100644
--- a/net_ip_socket.go
+++ b/net_ip_socket.go
@@ -141,7 +141,7 @@ func parseIP(hexIP string) (net.IP, error) {
 	var byteIP []byte
 	byteIP, err := hex.DecodeString(hexIP)
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse socket field in %q: %w", ErrFileParse, hexIP, err)
+		return nil, fmt.Errorf("%w: Cannot parse socket field in %q: %w", ErrFileParse, hexIP, err)
 	}
 	switch len(byteIP) {
 	case 4:
@@ -155,7 +155,7 @@ func parseIP(hexIP string) (net.IP, error) {
 		}
 		return i, nil
 	default:
-		return nil, fmt.Errorf("%s: Unable to parse IP %s: %w", ErrFileParse, hexIP, nil)
+		return nil, fmt.Errorf("%w: Unable to parse IP %s: %v", ErrFileParse, hexIP, nil)
 	}
 }
 
@@ -178,7 +178,7 @@ func parseNetIPSocketLine(fields []string, isUDP bool) (*netIPSocketLine, error)
 	}
 
 	if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
-		return nil, fmt.Errorf("%s: Unable to parse sl field in %q: %w", ErrFileParse, line.Sl, err)
+		return nil, fmt.Errorf("%w: Unable to parse sl field in %q: %w", ErrFileParse, line.Sl, err)
 	}
 	// local_address
 	l := strings.Split(fields[1], ":")
@@ -189,7 +189,7 @@ func parseNetIPSocketLine(fields []string, isUDP bool) (*netIPSocketLine, error)
 		return nil, err
 	}
 	if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
-		return nil, fmt.Errorf("%s: Unable to parse local_address port value line %q: %w", ErrFileParse, line.LocalPort, err)
+		return nil, fmt.Errorf("%w: Unable to parse local_address port value line %q: %w", ErrFileParse, line.LocalPort, err)
 	}
 
 	// remote_address
@@ -201,12 +201,12 @@ func parseNetIPSocketLine(fields []string, isUDP bool) (*netIPSocketLine, error)
 		return nil, err
 	}
 	if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse rem_address port value in %q: %w", ErrFileParse, line.RemPort, err)
+		return nil, fmt.Errorf("%w: Cannot parse rem_address port value in %q: %w", ErrFileParse, line.RemPort, err)
 	}
 
 	// st
 	if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse st value in %q: %w", ErrFileParse, line.St, err)
+		return nil, fmt.Errorf("%w: Cannot parse st value in %q: %w", ErrFileParse, line.St, err)
 	}
 
 	// tx_queue and rx_queue
@@ -219,27 +219,27 @@ func parseNetIPSocketLine(fields []string, isUDP bool) (*netIPSocketLine, error)
 		)
 	}
 	if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse tx_queue value in %q: %w", ErrFileParse, line.TxQueue, err)
+		return nil, fmt.Errorf("%w: Cannot parse tx_queue value in %q: %w", ErrFileParse, line.TxQueue, err)
 	}
 	if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse trx_queue value in %q: %w", ErrFileParse, line.RxQueue, err)
+		return nil, fmt.Errorf("%w: Cannot parse trx_queue value in %q: %w", ErrFileParse, line.RxQueue, err)
 	}
 
 	// uid
 	if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse UID value in %q: %w", ErrFileParse, line.UID, err)
+		return nil, fmt.Errorf("%w: Cannot parse UID value in %q: %w", ErrFileParse, line.UID, err)
 	}
 
 	// inode
 	if line.Inode, err = strconv.ParseUint(fields[9], 0, 64); err != nil {
-		return nil, fmt.Errorf("%s: Cannot parse inode value in %q: %w", ErrFileParse, line.Inode, err)
+		return nil, fmt.Errorf("%w: Cannot parse inode value in %q: %w", ErrFileParse, line.Inode, err)
 	}
 
 	// drops
 	if isUDP {
 		drops, err := strconv.ParseUint(fields[12], 0, 64)
 		if err != nil {
-			return nil, fmt.Errorf("%s: Cannot parse drops value in %q: %w", ErrFileParse, drops, err)
+			return nil, fmt.Errorf("%w: Cannot parse drops value in %q: %w", ErrFileParse, drops, err)
 		}
 		line.Drops = &drops
 	}
diff --git a/net_sockstat.go b/net_sockstat.go
index 360e36a..fae62b1 100644
--- a/net_sockstat.go
+++ b/net_sockstat.go
@@ -69,7 +69,7 @@ func readSockstat(name string) (*NetSockstat, error) {
 
 	stat, err := parseSockstat(bytes.NewReader(b))
 	if err != nil {
-		return nil, fmt.Errorf("%s: sockstats from %q: %w", ErrFileRead, name, err)
+		return nil, fmt.Errorf("%w: sockstats from %q: %w", ErrFileRead, name, err)
 	}
 
 	return stat, nil
@@ -89,7 +89,7 @@ func parseSockstat(r io.Reader) (*NetSockstat, error) {
 		// The remaining fields are key/value pairs.
 		kvs, err := parseSockstatKVs(fields[1:])
 		if err != nil {
-			return nil, fmt.Errorf("%s: sockstat key/value pairs from %q: %w", ErrFileParse, s.Text(), err)
+			return nil, fmt.Errorf("%w: sockstat key/value pairs from %q: %w", ErrFileParse, s.Text(), err)
 		}
 
 		// The first field is the protocol. We must trim its colon suffix.
diff --git a/net_softnet.go b/net_softnet.go
index c770852..71c8059 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -64,7 +64,7 @@ func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
 
 	entries, err := parseSoftnet(bytes.NewReader(b))
 	if err != nil {
-		return nil, fmt.Errorf("%s: /proc/net/softnet_stat: %w", ErrFileParse, err)
+		return nil, fmt.Errorf("%w: /proc/net/softnet_stat: %w", ErrFileParse, err)
 	}
 
 	return entries, nil
diff --git a/net_unix.go b/net_unix.go
index acbbc57..d868ceb 100644
--- a/net_unix.go
+++ b/net_unix.go
@@ -108,14 +108,14 @@ func parseNetUNIX(r io.Reader) (*NetUNIX, error) {
 		line := s.Text()
 		item, err := nu.parseLine(line, hasInode, minFields)
 		if err != nil {
-			return nil, fmt.Errorf("%s: /proc/net/unix encountered data %q: %w", ErrFileParse, line, err)
+			return nil, fmt.Errorf("%w: /proc/net/unix encountered data %q: %w", ErrFileParse, line, err)
 		}
 
 		nu.Rows = append(nu.Rows, item)
 	}
 
 	if err := s.Err(); err != nil {
-		return nil, fmt.Errorf("%s: /proc/net/unix encountered data: %w", ErrFileParse, err)
+		return nil, fmt.Errorf("%w: /proc/net/unix encountered data: %w", ErrFileParse, err)
 	}
 
 	return &nu, nil
@@ -136,29 +136,29 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine,
 
 	users, err := u.parseUsers(fields[1])
 	if err != nil {
-		return nil, fmt.Errorf("%s: ref count %q: %w", ErrFileParse, fields[1], err)
+		return nil, fmt.Errorf("%w: ref count %q: %w", ErrFileParse, fields[1], err)
 	}
 
 	flags, err := u.parseFlags(fields[3])
 	if err != nil {
-		return nil, fmt.Errorf("%s: Unable to parse flags %q: %w", ErrFileParse, fields[3], err)
+		return nil, fmt.Errorf("%w: Unable to parse flags %q: %w", ErrFileParse, fields[3], err)
 	}
 
 	typ, err := u.parseType(fields[4])
 	if err != nil {
-		return nil, fmt.Errorf("%s: Failed to parse type %q: %w", ErrFileParse, fields[4], err)
+		return nil, fmt.Errorf("%w: Failed to parse type %q: %w", ErrFileParse, fields[4], err)
 	}
 
 	state, err := u.parseState(fields[5])
 	if err != nil {
-		return nil, fmt.Errorf("%s: Failed to parse state %q: %w", ErrFileParse, fields[5], err)
+		return nil, fmt.Errorf("%w: Failed to parse state %q: %w", ErrFileParse, fields[5], err)
 	}
 
 	var inode uint64
 	if hasInode {
 		inode, err = u.parseInode(fields[6])
 		if err != nil {
-			return nil, fmt.Errorf("%s failed to parse inode %q: %w", ErrFileParse, fields[6], err)
+			return nil, fmt.Errorf("%w failed to parse inode %q: %w", ErrFileParse, fields[6], err)
 		}
 	}
 
diff --git a/net_wireless.go b/net_wireless.go
index 7443edc..7c597bc 100644
--- a/net_wireless.go
+++ b/net_wireless.go
@@ -68,7 +68,7 @@ func (fs FS) Wireless() ([]*Wireless, error) {
 
 	m, err := parseWireless(bytes.NewReader(b))
 	if err != nil {
-		return nil, fmt.Errorf("%s: wireless: %w", ErrFileParse, err)
+		return nil, fmt.Errorf("%w: wireless: %w", ErrFileParse, err)
 	}
 
 	return m, nil
@@ -114,47 +114,47 @@ func parseWireless(r io.Reader) ([]*Wireless, error) {
 
 		qlink, err := strconv.Atoi(strings.TrimSuffix(stats[1], "."))
 		if err != nil {
-			return nil, fmt.Errorf("%s: parse Quality:link as integer %q: %w", ErrFileParse, qlink, err)
+			return nil, fmt.Errorf("%w: parse Quality:link as integer %q: %w", ErrFileParse, qlink, err)
 		}
 
 		qlevel, err := strconv.Atoi(strings.TrimSuffix(stats[2], "."))
 		if err != nil {
-			return nil, fmt.Errorf("%s: Quality:level as integer %q: %w", ErrFileParse, qlevel, err)
+			return nil, fmt.Errorf("%w: Quality:level as integer %q: %w", ErrFileParse, qlevel, err)
 		}
 
 		qnoise, err := strconv.Atoi(strings.TrimSuffix(stats[3], "."))
 		if err != nil {
-			return nil, fmt.Errorf("%s: Quality:noise as integer %q: %w", ErrFileParse, qnoise, err)
+			return nil, fmt.Errorf("%w: Quality:noise as integer %q: %w", ErrFileParse, qnoise, err)
 		}
 
 		dnwid, err := strconv.Atoi(stats[4])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Discarded:nwid as integer %q: %w", ErrFileParse, dnwid, err)
+			return nil, fmt.Errorf("%w: Discarded:nwid as integer %q: %w", ErrFileParse, dnwid, err)
 		}
 
 		dcrypt, err := strconv.Atoi(stats[5])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Discarded:crypt as integer %q: %w", ErrFileParse, dcrypt, err)
+			return nil, fmt.Errorf("%w: Discarded:crypt as integer %q: %w", ErrFileParse, dcrypt, err)
 		}
 
 		dfrag, err := strconv.Atoi(stats[6])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Discarded:frag as integer %q: %w", ErrFileParse, dfrag, err)
+			return nil, fmt.Errorf("%w: Discarded:frag as integer %q: %w", ErrFileParse, dfrag, err)
 		}
 
 		dretry, err := strconv.Atoi(stats[7])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Discarded:retry as integer %q: %w", ErrFileParse, dretry, err)
+			return nil, fmt.Errorf("%w: Discarded:retry as integer %q: %w", ErrFileParse, dretry, err)
 		}
 
 		dmisc, err := strconv.Atoi(stats[8])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Discarded:misc as integer %q: %w", ErrFileParse, dmisc, err)
+			return nil, fmt.Errorf("%w: Discarded:misc as integer %q: %w", ErrFileParse, dmisc, err)
 		}
 
 		mbeacon, err := strconv.Atoi(stats[9])
 		if err != nil {
-			return nil, fmt.Errorf("%s: Missed:beacon as integer %q: %w", ErrFileParse, mbeacon, err)
+			return nil, fmt.Errorf("%w: Missed:beacon as integer %q: %w", ErrFileParse, mbeacon, err)
 		}
 
 		w := &Wireless{
@@ -175,7 +175,7 @@ func parseWireless(r io.Reader) ([]*Wireless, error) {
 	}
 
 	if err := scanner.Err(); err != nil {
-		return nil, fmt.Errorf("%s: Failed to scan /proc/net/wireless: %w", ErrFileRead, err)
+		return nil, fmt.Errorf("%w: Failed to scan /proc/net/wireless: %w", ErrFileRead, err)
 	}
 
 	return interfaces, nil
diff --git a/proc.go b/proc.go
index d1f71ca..0e8c4fa 100644
--- a/proc.go
+++ b/proc.go
@@ -111,7 +111,7 @@ func (fs FS) AllProcs() (Procs, error) {
 
 	names, err := d.Readdirnames(-1)
 	if err != nil {
-		return Procs{}, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, names, err)
+		return Procs{}, fmt.Errorf("%w: Cannot read file: %v: %w", ErrFileRead, names, err)
 	}
 
 	p := Procs{}
@@ -212,7 +212,7 @@ func (p Proc) FileDescriptors() ([]uintptr, error) {
 	for i, n := range names {
 		fd, err := strconv.ParseInt(n, 10, 32)
 		if err != nil {
-			return nil, fmt.Errorf("%s: Cannot parse line: %v: %w", ErrFileParse, i, err)
+			return nil, fmt.Errorf("%w: Cannot parse line: %v: %w", ErrFileParse, i, err)
 		}
 		fds[i] = uintptr(fd)
 	}
@@ -297,7 +297,7 @@ func (p Proc) fileDescriptors() ([]string, error) {
 
 	names, err := d.Readdirnames(-1)
 	if err != nil {
-		return nil, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, names, err)
+		return nil, fmt.Errorf("%w: Cannot read file: %v: %w", ErrFileRead, names, err)
 	}
 
 	return names, nil
diff --git a/proc_limits.go b/proc_limits.go
index c86d815..9530b14 100644
--- a/proc_limits.go
+++ b/proc_limits.go
@@ -154,7 +154,7 @@ func parseUint(s string) (uint64, error) {
 	}
 	i, err := strconv.ParseUint(s, 10, 64)
 	if err != nil {
-		return 0, fmt.Errorf("%s: couldn't parse value %q: %w", ErrFileParse, s, err)
+		return 0, fmt.Errorf("%w: couldn't parse value %q: %w", ErrFileParse, s, err)
 	}
 	return i, nil
 }
diff --git a/proc_ns.go b/proc_ns.go
index c226667..0f8f847 100644
--- a/proc_ns.go
+++ b/proc_ns.go
@@ -40,7 +40,7 @@ func (p Proc) Namespaces() (Namespaces, error) {
 
 	names, err := d.Readdirnames(-1)
 	if err != nil {
-		return nil, fmt.Errorf("%s: failed to read contents of ns dir: %w", ErrFileRead, err)
+		return nil, fmt.Errorf("%w: failed to read contents of ns dir: %w", ErrFileRead, err)
 	}
 
 	ns := make(Namespaces, len(names))
@@ -58,7 +58,7 @@ func (p Proc) Namespaces() (Namespaces, error) {
 		typ := fields[0]
 		inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
 		if err != nil {
-			return nil, fmt.Errorf("%s: inode from %q: %w", ErrFileParse, fields[1], err)
+			return nil, fmt.Errorf("%w: inode from %q: %w", ErrFileParse, fields[1], err)
 		}
 
 		ns[name] = Namespace{typ, uint32(inode)}
diff --git a/proc_psi.go b/proc_psi.go
index fe9dbb4..ccd35f1 100644
--- a/proc_psi.go
+++ b/proc_psi.go
@@ -61,7 +61,7 @@ type PSIStats struct {
 func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
 	data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
 	if err != nil {
-		return PSIStats{}, fmt.Errorf("%s: psi_stats: unavailable for %q: %w", ErrFileRead, resource, err)
+		return PSIStats{}, fmt.Errorf("%w: psi_stats: unavailable for %q: %w", ErrFileRead, resource, err)
 	}
 
 	return parsePSIStats(bytes.NewReader(data))
diff --git a/proc_status.go b/proc_status.go
index 46307f5..a055197 100644
--- a/proc_status.go
+++ b/proc_status.go
@@ -15,6 +15,7 @@ package procfs
 
 import (
 	"bytes"
+	"math/bits"
 	"sort"
 	"strconv"
 	"strings"
@@ -76,9 +77,9 @@ type ProcStatus struct {
 	NonVoluntaryCtxtSwitches uint64
 
 	// UIDs of the process (Real, effective, saved set, and filesystem UIDs)
-	UIDs [4]string
+	UIDs [4]uint64
 	// GIDs of the process (Real, effective, saved set, and filesystem GIDs)
-	GIDs [4]string
+	GIDs [4]uint64
 
 	// CpusAllowedList: List of cpu cores processes are allowed to run on.
 	CpusAllowedList []uint64
@@ -113,22 +114,37 @@ func (p Proc) NewStatus() (ProcStatus, error) {
 		// convert kB to B
 		vBytes := vKBytes * 1024
 
-		s.fillStatus(k, v, vKBytes, vBytes)
+		err = s.fillStatus(k, v, vKBytes, vBytes)
+		if err != nil {
+			return ProcStatus{}, err
+		}
 	}
 
 	return s, nil
 }
 
-func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) {
+func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) error {
 	switch k {
 	case "Tgid":
 		s.TGID = int(vUint)
 	case "Name":
 		s.Name = vString
 	case "Uid":
-		copy(s.UIDs[:], strings.Split(vString, "\t"))
+		var err error
+		for i, v := range strings.Split(vString, "\t") {
+			s.UIDs[i], err = strconv.ParseUint(v, 10, bits.UintSize)
+			if err != nil {
+				return err
+			}
+		}
 	case "Gid":
-		copy(s.GIDs[:], strings.Split(vString, "\t"))
+		var err error
+		for i, v := range strings.Split(vString, "\t") {
+			s.GIDs[i], err = strconv.ParseUint(v, 10, bits.UintSize)
+			if err != nil {
+				return err
+			}
+		}
 	case "NSpid":
 		s.NSpids = calcNSPidsList(vString)
 	case "VmPeak":
@@ -173,6 +189,7 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt
 		s.CpusAllowedList = calcCpusAllowedList(vString)
 	}
 
+	return nil
 }
 
 // TotalCtxtSwitches returns the total context switch.
diff --git a/proc_sys.go b/proc_sys.go
index 12c5bf0..5eefbe2 100644
--- a/proc_sys.go
+++ b/proc_sys.go
@@ -44,7 +44,7 @@ func (fs FS) SysctlInts(sysctl string) ([]int, error) {
 		vp := util.NewValueParser(f)
 		values[i] = vp.Int()
 		if err := vp.Err(); err != nil {
-			return nil, fmt.Errorf("%s: field %d in sysctl %s is not a valid int: %w", ErrFileParse, i, sysctl, err)
+			return nil, fmt.Errorf("%w: field %d in sysctl %s is not a valid int: %w", ErrFileParse, i, sysctl, err)
 		}
 	}
 	return values, nil
diff --git a/softirqs.go b/softirqs.go
index b8fad67..28708e0 100644
--- a/softirqs.go
+++ b/softirqs.go
@@ -74,7 +74,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.Hi = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.Hi[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (HI%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (HI%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "TIMER:":
@@ -82,7 +82,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.Timer = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.Timer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (TIMER%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (TIMER%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "NET_TX:":
@@ -90,7 +90,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.NetTx = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.NetTx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (NET_TX%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (NET_TX%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "NET_RX:":
@@ -98,7 +98,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.NetRx = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.NetRx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (NET_RX%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (NET_RX%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "BLOCK:":
@@ -106,7 +106,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.Block = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.Block[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (BLOCK%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (BLOCK%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "IRQ_POLL:":
@@ -114,7 +114,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.IRQPoll = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.IRQPoll[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (IRQ_POLL%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (IRQ_POLL%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "TASKLET:":
@@ -122,7 +122,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.Tasklet = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.Tasklet[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (TASKLET%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (TASKLET%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "SCHED:":
@@ -130,7 +130,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.Sched = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.Sched[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (SCHED%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (SCHED%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "HRTIMER:":
@@ -138,7 +138,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.HRTimer = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.HRTimer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (HRTIMER%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (HRTIMER%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "RCU:":
@@ -146,14 +146,14 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
 			softirqs.RCU = make([]uint64, len(perCPU))
 			for i, count := range perCPU {
 				if softirqs.RCU[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (RCU%d): %w", ErrFileParse, count, i, err)
+					return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (RCU%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		}
 	}
 
 	if err := scanner.Err(); err != nil {
-		return Softirqs{}, fmt.Errorf("%s: couldn't parse softirqs: %w", ErrFileParse, err)
+		return Softirqs{}, fmt.Errorf("%w: couldn't parse softirqs: %w", ErrFileParse, err)
 	}
 
 	return softirqs, scanner.Err()
diff --git a/stat.go b/stat.go
index 34fc3ee..e36b41c 100644
--- a/stat.go
+++ b/stat.go
@@ -93,7 +93,7 @@ func parseCPUStat(line string) (CPUStat, int64, error) {
 		&cpuStat.Guest, &cpuStat.GuestNice)
 
 	if err != nil && err != io.EOF {
-		return CPUStat{}, -1, fmt.Errorf("%s: couldn't parse %q (cpu): %w", ErrFileParse, line, err)
+		return CPUStat{}, -1, fmt.Errorf("%w: couldn't parse %q (cpu): %w", ErrFileParse, line, err)
 	}
 	if count == 0 {
 		return CPUStat{}, -1, fmt.Errorf("%w: couldn't parse %q (cpu): 0 elements parsed", ErrFileParse, line)
@@ -116,7 +116,7 @@ func parseCPUStat(line string) (CPUStat, int64, error) {
 
 	cpuID, err := strconv.ParseInt(cpu[3:], 10, 64)
 	if err != nil {
-		return CPUStat{}, -1, fmt.Errorf("%s: couldn't parse %q (cpu/cpuid): %w", ErrFileParse, line, err)
+		return CPUStat{}, -1, fmt.Errorf("%w: couldn't parse %q (cpu/cpuid): %w", ErrFileParse, line, err)
 	}
 
 	return cpuStat, cpuID, nil
@@ -136,7 +136,7 @@ func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
 		&softIRQStat.Hrtimer, &softIRQStat.Rcu)
 
 	if err != nil {
-		return SoftIRQStat{}, 0, fmt.Errorf("%s: couldn't parse %q (softirq): %w", ErrFileParse, line, err)
+		return SoftIRQStat{}, 0, fmt.Errorf("%w: couldn't parse %q (softirq): %w", ErrFileParse, line, err)
 	}
 
 	return softIRQStat, total, nil
@@ -201,34 +201,34 @@ func parseStat(r io.Reader, fileName string) (Stat, error) {
 		switch {
 		case parts[0] == "btime":
 			if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (btime): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (btime): %w", ErrFileParse, parts[1], err)
 			}
 		case parts[0] == "intr":
 			if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (intr): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (intr): %w", ErrFileParse, parts[1], err)
 			}
 			numberedIRQs := parts[2:]
 			stat.IRQ = make([]uint64, len(numberedIRQs))
 			for i, count := range numberedIRQs {
 				if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil {
-					return Stat{}, fmt.Errorf("%s: couldn't parse %q (intr%d): %w", ErrFileParse, count, i, err)
+					return Stat{}, fmt.Errorf("%w: couldn't parse %q (intr%d): %w", ErrFileParse, count, i, err)
 				}
 			}
 		case parts[0] == "ctxt":
 			if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (ctxt): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (ctxt): %w", ErrFileParse, parts[1], err)
 			}
 		case parts[0] == "processes":
 			if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (processes): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (processes): %w", ErrFileParse, parts[1], err)
 			}
 		case parts[0] == "procs_running":
 			if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (procs_running): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (procs_running): %w", ErrFileParse, parts[1], err)
 			}
 		case parts[0] == "procs_blocked":
 			if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
-				return Stat{}, fmt.Errorf("%s: couldn't parse %q (procs_blocked): %w", ErrFileParse, parts[1], err)
+				return Stat{}, fmt.Errorf("%w: couldn't parse %q (procs_blocked): %w", ErrFileParse, parts[1], err)
 			}
 		case parts[0] == "softirq":
 			softIRQStats, total, err := parseSoftIRQStat(line)
@@ -251,7 +251,7 @@ func parseStat(r io.Reader, fileName string) (Stat, error) {
 	}
 
 	if err := scanner.Err(); err != nil {
-		return Stat{}, fmt.Errorf("%s: couldn't parse %q: %w", ErrFileParse, fileName, err)
+		return Stat{}, fmt.Errorf("%w: couldn't parse %q: %w", ErrFileParse, fileName, err)
 	}
 
 	return stat, nil
diff --git a/swaps.go b/swaps.go
index fa00f55..65fec83 100644
--- a/swaps.go
+++ b/swaps.go
@@ -74,15 +74,15 @@ func parseSwapString(swapString string) (*Swap, error) {
 
 	swap.Size, err = strconv.Atoi(swapFields[2])
 	if err != nil {
-		return nil, fmt.Errorf("%s: invalid swap size: %s: %w", ErrFileParse, swapFields[2], err)
+		return nil, fmt.Errorf("%w: invalid swap size: %s: %w", ErrFileParse, swapFields[2], err)
 	}
 	swap.Used, err = strconv.Atoi(swapFields[3])
 	if err != nil {
-		return nil, fmt.Errorf("%s: invalid swap used: %s: %w", ErrFileParse, swapFields[3], err)
+		return nil, fmt.Errorf("%w: invalid swap used: %s: %w", ErrFileParse, swapFields[3], err)
 	}
 	swap.Priority, err = strconv.Atoi(swapFields[4])
 	if err != nil {
-		return nil, fmt.Errorf("%s: invalid swap priority: %s: %w", ErrFileParse, swapFields[4], err)
+		return nil, fmt.Errorf("%w: invalid swap priority: %s: %w", ErrFileParse, swapFields[4], err)
 	}
 
 	return swap, nil
diff --git a/sysfs/class_fibrechannel.go b/sysfs/class_fibrechannel.go
index ded07a8..4b583f7 100644
--- a/sysfs/class_fibrechannel.go
+++ b/sysfs/class_fibrechannel.go
@@ -28,36 +28,36 @@ import (
 const fibrechannelClassPath = "class/fc_host"
 
 type FibreChannelCounters struct {
-	DumpedFrames          uint64 // /sys/class/fc_host/<Name>/statistics/dumped_frames
-	ErrorFrames           uint64 // /sys/class/fc_host/<Name>/statistics/error_frames
-	InvalidCRCCount       uint64 // /sys/class/fc_host/<Name>/statistics/invalid_crc_count
-	RXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/rx_frames
-	RXWords               uint64 // /sys/class/fc_host/<Name>/statistics/rx_words
-	TXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/tx_frames
-	TXWords               uint64 // /sys/class/fc_host/<Name>/statistics/tx_words
-	SecondsSinceLastReset uint64 // /sys/class/fc_host/<Name>/statistics/seconds_since_last_reset
-	InvalidTXWordCount    uint64 // /sys/class/fc_host/<Name>/statistics/invalid_tx_word_count
-	LinkFailureCount      uint64 // /sys/class/fc_host/<Name>/statistics/link_failure_count
-	LossOfSyncCount       uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_sync_count
-	LossOfSignalCount     uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_signal_count
-	NosCount              uint64 // /sys/class/fc_host/<Name>/statistics/nos_count
-	FCPPacketAborts       uint64 // / sys/class/fc_host/<Name>/statistics/fcp_packet_aborts
+	DumpedFrames          *uint64 // /sys/class/fc_host/<Name>/statistics/dumped_frames
+	ErrorFrames           *uint64 // /sys/class/fc_host/<Name>/statistics/error_frames
+	InvalidCRCCount       *uint64 // /sys/class/fc_host/<Name>/statistics/invalid_crc_count
+	RXFrames              *uint64 // /sys/class/fc_host/<Name>/statistics/rx_frames
+	RXWords               *uint64 // /sys/class/fc_host/<Name>/statistics/rx_words
+	TXFrames              *uint64 // /sys/class/fc_host/<Name>/statistics/tx_frames
+	TXWords               *uint64 // /sys/class/fc_host/<Name>/statistics/tx_words
+	SecondsSinceLastReset *uint64 // /sys/class/fc_host/<Name>/statistics/seconds_since_last_reset
+	InvalidTXWordCount    *uint64 // /sys/class/fc_host/<Name>/statistics/invalid_tx_word_count
+	LinkFailureCount      *uint64 // /sys/class/fc_host/<Name>/statistics/link_failure_count
+	LossOfSyncCount       *uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_sync_count
+	LossOfSignalCount     *uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_signal_count
+	NosCount              *uint64 // /sys/class/fc_host/<Name>/statistics/nos_count
+	FCPPacketAborts       *uint64 // /sys/class/fc_host/<Name>/statistics/fcp_packet_aborts
 }
 
 type FibreChannelHost struct {
-	Name             string               // /sys/class/fc_host/<Name>
-	Speed            string               // /sys/class/fc_host/<Name>/speed
-	PortState        string               // /sys/class/fc_host/<Name>/port_state
-	PortType         string               // /sys/class/fc_host/<Name>/port_type
-	SymbolicName     string               // /sys/class/fc_host/<Name>/symbolic_name
-	NodeName         string               // /sys/class/fc_host/<Name>/node_name
-	PortID           string               // /sys/class/fc_host/<Name>/port_id
-	PortName         string               // /sys/class/fc_host/<Name>/port_name
-	FabricName       string               // /sys/class/fc_host/<Name>/fabric_name
-	DevLossTMO       string               // /sys/class/fc_host/<Name>/dev_loss_tmo
-	SupportedClasses string               // /sys/class/fc_host/<Name>/supported_classes
-	SupportedSpeeds  string               // /sys/class/fc_host/<Name>/supported_speeds
-	Counters         FibreChannelCounters // /sys/class/fc_host/<Name>/statistics/*
+	Name             *string               // /sys/class/fc_host/<Name>
+	Speed            *string               // /sys/class/fc_host/<Name>/speed
+	PortState        *string               // /sys/class/fc_host/<Name>/port_state
+	PortType         *string               // /sys/class/fc_host/<Name>/port_type
+	SymbolicName     *string               // /sys/class/fc_host/<Name>/symbolic_name
+	NodeName         *string               // /sys/class/fc_host/<Name>/node_name
+	PortID           *string               // /sys/class/fc_host/<Name>/port_id
+	PortName         *string               // /sys/class/fc_host/<Name>/port_name
+	FabricName       *string               // /sys/class/fc_host/<Name>/fabric_name
+	DevLossTMO       *string               // /sys/class/fc_host/<Name>/dev_loss_tmo
+	SupportedClasses *string               // /sys/class/fc_host/<Name>/supported_classes
+	SupportedSpeeds  *string               // /sys/class/fc_host/<Name>/supported_speeds
+	Counters         *FibreChannelCounters // /sys/class/fc_host/<Name>/statistics/*
 }
 
 type FibreChannelClass map[string]FibreChannelHost
@@ -78,7 +78,7 @@ func (fs FS) FibreChannelClass() (FibreChannelClass, error) {
 			return nil, err
 		}
 
-		fcc[host.Name] = *host
+		fcc[*host.Name] = *host
 	}
 
 	return fcc, nil
@@ -87,7 +87,7 @@ func (fs FS) FibreChannelClass() (FibreChannelClass, error) {
 // Parse a single FC host.
 func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 	path := fs.sys.Path(fibrechannelClassPath, name)
-	host := FibreChannelHost{Name: name}
+	host := FibreChannelHost{Name: &name}
 
 	for _, f := range [...]string{"speed", "port_state", "port_type", "node_name", "port_id", "port_name", "fabric_name", "dev_loss_tmo", "symbolic_name", "supported_classes", "supported_speeds"} {
 		name := filepath.Join(path, f)
@@ -103,39 +103,39 @@ func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 
 		switch f {
 		case "speed":
-			host.Speed = value
+			host.Speed = &value
 		case "port_state":
-			host.PortState = value
+			host.PortState = &value
 		case "port_type":
-			host.PortType = value
+			host.PortType = &value
 		case "node_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.NodeName = value
+			host.NodeName = &value
 		case "port_id":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.PortID = value
+			host.PortID = &value
 		case "port_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.PortName = value
+			host.PortName = &value
 		case "fabric_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.FabricName = value
+			host.FabricName = &value
 		case "dev_loss_tmo":
-			host.DevLossTMO = value
+			host.DevLossTMO = &value
 		case "supported_classes":
-			host.SupportedClasses = value
+			host.SupportedClasses = &value
 		case "supported_speeds":
-			host.SupportedSpeeds = value
+			host.SupportedSpeeds = &value
 		case "symbolic_name":
-			host.SymbolicName = value
+			host.SymbolicName = &value
 		}
 	}
 
@@ -143,7 +143,7 @@ func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 	if err != nil {
 		return nil, err
 	}
-	host.Counters = *counters
+	host.Counters = counters
 
 	return &host, nil
 }
@@ -178,9 +178,9 @@ func parseFibreChannelStatistics(hostPath string) (*FibreChannelCounters, error)
 		// Below switch was automatically generated. Don't need everything in there yet, so the unwanted bits are commented out.
 		switch f.Name() {
 		case "dumped_frames":
-			counters.DumpedFrames = *vp.PUInt64()
+			counters.DumpedFrames = vp.PUInt64()
 		case "error_frames":
-			counters.ErrorFrames = *vp.PUInt64()
+			counters.ErrorFrames = vp.PUInt64()
 		/*
 			case "fc_no_free_exch":
 				counters.FcNoFreeExch = *vp.PUInt64()
@@ -208,41 +208,41 @@ func parseFibreChannelStatistics(hostPath string) (*FibreChannelCounters, error)
 				counters.FcpOutputRequests = *vp.PUInt64()
 		*/
 		case "fcp_packet_aborts":
-			counters.FCPPacketAborts = *vp.PUInt64()
+			counters.FCPPacketAborts = vp.PUInt64()
 			/*
 				case "fcp_packet_alloc_failures":
 					counters.FcpPacketAllocFailures = *vp.PUInt64()
 			*/
 		case "invalid_tx_word_count":
-			counters.InvalidTXWordCount = *vp.PUInt64()
+			counters.InvalidTXWordCount = vp.PUInt64()
 		case "invalid_crc_count":
-			counters.InvalidCRCCount = *vp.PUInt64()
+			counters.InvalidCRCCount = vp.PUInt64()
 		case "link_failure_count":
-			counters.LinkFailureCount = *vp.PUInt64()
+			counters.LinkFailureCount = vp.PUInt64()
 		/*
 			case "lip_count":
 					counters.LipCount = *vp.PUInt64()
 		*/
 		case "loss_of_signal_count":
-			counters.LossOfSignalCount = *vp.PUInt64()
+			counters.LossOfSignalCount = vp.PUInt64()
 		case "loss_of_sync_count":
-			counters.LossOfSyncCount = *vp.PUInt64()
+			counters.LossOfSyncCount = vp.PUInt64()
 		case "nos_count":
-			counters.NosCount = *vp.PUInt64()
+			counters.NosCount = vp.PUInt64()
 		/*
 			case "prim_seq_protocol_err_count":
 				counters.PrimSeqProtocolErrCount = *vp.PUInt64()
 		*/
 		case "rx_frames":
-			counters.RXFrames = *vp.PUInt64()
+			counters.RXFrames = vp.PUInt64()
 		case "rx_words":
-			counters.RXWords = *vp.PUInt64()
+			counters.RXWords = vp.PUInt64()
 		case "seconds_since_last_reset":
-			counters.SecondsSinceLastReset = *vp.PUInt64()
+			counters.SecondsSinceLastReset = vp.PUInt64()
 		case "tx_frames":
-			counters.TXFrames = *vp.PUInt64()
+			counters.TXFrames = vp.PUInt64()
 		case "tx_words":
-			counters.TXWords = *vp.PUInt64()
+			counters.TXWords = vp.PUInt64()
 		}
 
 		if err := vp.Err(); err != nil {
diff --git a/thread.go b/thread.go
index df2215e..80e0e94 100644
--- a/thread.go
+++ b/thread.go
@@ -45,7 +45,7 @@ func (fs FS) AllThreads(pid int) (Procs, error) {
 
 	names, err := d.Readdirnames(-1)
 	if err != nil {
-		return Procs{}, fmt.Errorf("%s: could not read %q: %w", ErrFileRead, d.Name(), err)
+		return Procs{}, fmt.Errorf("%w: could not read %q: %w", ErrFileRead, d.Name(), err)
 	}
 
 	t := Procs{}
diff --git a/zoneinfo.go b/zoneinfo.go
index ce5fefa..e54d94b 100644
--- a/zoneinfo.go
+++ b/zoneinfo.go
@@ -75,11 +75,11 @@ var nodeZoneRE = regexp.MustCompile(`(\d+), zone\s+(\w+)`)
 func (fs FS) Zoneinfo() ([]Zoneinfo, error) {
 	data, err := os.ReadFile(fs.proc.Path("zoneinfo"))
 	if err != nil {
-		return nil, fmt.Errorf("%s: error reading zoneinfo %q: %w", ErrFileRead, fs.proc.Path("zoneinfo"), err)
+		return nil, fmt.Errorf("%w: error reading zoneinfo %q: %w", ErrFileRead, fs.proc.Path("zoneinfo"), err)
 	}
 	zoneinfo, err := parseZoneinfo(data)
 	if err != nil {
-		return nil, fmt.Errorf("%s: error parsing zoneinfo %q: %w", ErrFileParse, fs.proc.Path("zoneinfo"), err)
+		return nil, fmt.Errorf("%w: error parsing zoneinfo %q: %w", ErrFileParse, fs.proc.Path("zoneinfo"), err)
 	}
 	return zoneinfo, nil
 }


Test patch:
diff --git a/mountstats_test.go b/mountstats_test.go
index 43903e9..262f7d1 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -116,21 +116,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.0",
-					Transport: NFSTransportStats{
-						Protocol:                 "tcp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  3,
-						ConnectIdleTime:          4,
-						IdleTimeSeconds:          5,
-						Sends:                    6,
-						Receives:                 7,
-						BadTransactionIDs:        8,
-						CumulativeActiveRequests: 9,
-						CumulativeBacklog:        10,
-						MaximumRPCSlotsUsed:      0, // these three are not
-						CumulativeSendingQueue:   0, // present in statvers=1.0
-						CumulativePendingQueue:   0, //
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "tcp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  3,
+							ConnectIdleTime:          4,
+							IdleTimeSeconds:          5,
+							Sends:                    6,
+							Receives:                 7,
+							BadTransactionIDs:        8,
+							CumulativeActiveRequests: 9,
+							CumulativeBacklog:        10,
+							MaximumRPCSlotsUsed:      0, // these three are not
+							CumulativeSendingQueue:   0, // present in statvers=1.0
+							CumulativePendingQueue:   0, //
+						},
 					},
 				},
 			}},
@@ -144,21 +146,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.0",
-					Transport: NFSTransportStats{
-						Protocol:                 "udp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  0,
-						ConnectIdleTime:          0,
-						IdleTimeSeconds:          0,
-						Sends:                    3,
-						Receives:                 4,
-						BadTransactionIDs:        5,
-						CumulativeActiveRequests: 6,
-						CumulativeBacklog:        7,
-						MaximumRPCSlotsUsed:      0, // these three are not
-						CumulativeSendingQueue:   0, // present in statvers=1.0
-						CumulativePendingQueue:   0, //
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "udp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  0,
+							ConnectIdleTime:          0,
+							IdleTimeSeconds:          0,
+							Sends:                    3,
+							Receives:                 4,
+							BadTransactionIDs:        5,
+							CumulativeActiveRequests: 6,
+							CumulativeBacklog:        7,
+							MaximumRPCSlotsUsed:      0, // these three are not
+							CumulativeSendingQueue:   0, // present in statvers=1.0
+							CumulativePendingQueue:   0, //
+						},
 					},
 				},
 			}},
@@ -172,21 +176,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.1",
-					Transport: NFSTransportStats{
-						Protocol:                 "tcp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  3,
-						ConnectIdleTime:          4,
-						IdleTimeSeconds:          5,
-						Sends:                    6,
-						Receives:                 7,
-						BadTransactionIDs:        8,
-						CumulativeActiveRequests: 9,
-						CumulativeBacklog:        10,
-						MaximumRPCSlotsUsed:      11,
-						CumulativeSendingQueue:   12,
-						CumulativePendingQueue:   13,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "tcp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  3,
+							ConnectIdleTime:          4,
+							IdleTimeSeconds:          5,
+							Sends:                    6,
+							Receives:                 7,
+							BadTransactionIDs:        8,
+							CumulativeActiveRequests: 9,
+							CumulativeBacklog:        10,
+							MaximumRPCSlotsUsed:      11,
+							CumulativeSendingQueue:   12,
+							CumulativePendingQueue:   13,
+						},
 					},
 				},
 			}},
@@ -200,21 +206,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.1",
-					Transport: NFSTransportStats{
-						Protocol:                 "udp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  0, // these three are not
-						ConnectIdleTime:          0, // present for UDP
-						IdleTimeSeconds:          0, //
-						Sends:                    3,
-						Receives:                 4,
-						BadTransactionIDs:        5,
-						CumulativeActiveRequests: 6,
-						CumulativeBacklog:        7,
-						MaximumRPCSlotsUsed:      8,
-						CumulativeSendingQueue:   9,
-						CumulativePendingQueue:   10,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "udp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  0, // these three are not
+							ConnectIdleTime:          0, // present for UDP
+							IdleTimeSeconds:          0, //
+							Sends:                    3,
+							Receives:                 4,
+							BadTransactionIDs:        5,
+							CumulativeActiveRequests: 6,
+							CumulativeBacklog:        7,
+							MaximumRPCSlotsUsed:      8,
+							CumulativeSendingQueue:   9,
+							CumulativePendingQueue:   10,
+						},
 					},
 				},
 			}},
@@ -339,7 +347,6 @@ func TestMountStats(t *testing.T) {
 								CumulativeQueueMilliseconds:         6,
 								CumulativeTotalResponseMilliseconds: 79386,
 								CumulativeTotalRequestMilliseconds:  79407,
-								AverageRTTMilliseconds:              61.16024653312789,
 							},
 							{
 								Operation: "WRITE",
@@ -353,20 +360,21 @@ func TestMountStats(t *testing.T) {
 								CumulativeQueueMilliseconds:         18446743919241604546,
 								CumulativeTotalResponseMilliseconds: 1667369447,
 								CumulativeTotalRequestMilliseconds:  1953587717,
-								AverageRTTMilliseconds:              0.5695744656983355,
 							},
 						},
-						Transport: NFSTransportStats{
-							Protocol:                 "tcp",
-							Port:                     832,
-							Connect:                  1,
-							IdleTimeSeconds:          11,
-							Sends:                    6428,
-							Receives:                 6428,
-							CumulativeActiveRequests: 12154,
-							MaximumRPCSlotsUsed:      24,
-							CumulativeSendingQueue:   26,
-							CumulativePendingQueue:   5726,
+						Transport: []NFSTransportStats{
+							{
+								Protocol:                 "tcp",
+								Port:                     832,
+								Connect:                  1,
+								IdleTimeSeconds:          11,
+								Sends:                    6428,
+								Receives:                 6428,
+								CumulativeActiveRequests: 12154,
+								MaximumRPCSlotsUsed:      24,
+								CumulativeSendingQueue:   26,
+								CumulativePendingQueue:   5726,
+							},
 						},
 					},
 				},
@@ -427,43 +435,44 @@ func TestMountStats(t *testing.T) {
 							CumulativeQueueMilliseconds:         320,
 							CumulativeTotalResponseMilliseconds: 11,
 							CumulativeTotalRequestMilliseconds:  331,
-							AverageRTTMilliseconds:              0.6875,
 							Errors:                              0,
 						},
 					},
-					Transport: NFSTransportStats{
-						Protocol:                 "rdma",
-						Port:                     0,
-						Bind:                     0,
-						Connect:                  5808,
-						ConnectIdleTime:          62,
-						IdleTimeSeconds:          0,
-						Sends:                    494490723,
-						Receives:                 494490687,
-						BadTransactionIDs:        36,
-						CumulativeActiveRequests: 10032963746,
-						CumulativeBacklog:        1282789,
-						MaximumRPCSlotsUsed:      0,
-						CumulativeSendingQueue:   0,
-						CumulativePendingQueue:   0,
-						ReadChunkCount:           107150285,
-						WriteChunkCount:          1226637531,
-						ReplyChunkCount:          2673889,
-						TotalRdmaRequest:         135120843409861,
-						PullupCopyCount:          135119397156505,
-						HardwayRegisterCount:     266368832,
-						FailedMarshalCount:       75716996,
-						BadReplyCount:            0,
-						MrsRecovered:             7853,
-						MrsOrphaned:              0,
-						MrsAllocated:             0,
-						EmptySendctxQ:            0,
-						TotalRdmaReply:           0,
-						FixupCopyCount:           119328,
-						ReplyWaitsForSend:        1336431717,
-						LocalInvNeeded:           0,
-						NomsgCallCount:           96,
-						BcallCount:               0,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "rdma",
+							Port:                     0,
+							Bind:                     0,
+							Connect:                  5808,
+							ConnectIdleTime:          62,
+							IdleTimeSeconds:          0,
+							Sends:                    494490723,
+							Receives:                 494490687,
+							BadTransactionIDs:        36,
+							CumulativeActiveRequests: 10032963746,
+							CumulativeBacklog:        1282789,
+							MaximumRPCSlotsUsed:      0,
+							CumulativeSendingQueue:   0,
+							CumulativePendingQueue:   0,
+							ReadChunkCount:           107150285,
+							WriteChunkCount:          1226637531,
+							ReplyChunkCount:          2673889,
+							TotalRdmaRequest:         135120843409861,
+							PullupCopyCount:          135119397156505,
+							HardwayRegisterCount:     266368832,
+							FailedMarshalCount:       75716996,
+							BadReplyCount:            0,
+							MrsRecovered:             7853,
+							MrsOrphaned:              0,
+							MrsAllocated:             0,
+							EmptySendctxQ:            0,
+							TotalRdmaReply:           0,
+							FixupCopyCount:           119328,
+							ReplyWaitsForSend:        1336431717,
+							LocalInvNeeded:           0,
+							NomsgCallCount:           96,
+							BcallCount:               0,
+						},
 					},
 				},
 			}},
diff --git a/proc_status_test.go b/proc_status_test.go
index 9d22a48..9644bf0 100644
--- a/proc_status_test.go
+++ b/proc_status_test.go
@@ -103,8 +103,8 @@ func TestProcStatusUIDs(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if want, have := [4]string{"1000", "1000", "1000", "0"}, s.UIDs; want != have {
-		t.Errorf("want uids %s, have %s", want, have)
+	if want, have := [4]uint64{1000, 1000, 1000, 0}, s.UIDs; want != have {
+		t.Errorf("want uids %v, have %v", want, have)
 	}
 }
 
@@ -119,8 +119,8 @@ func TestProcStatusGIDs(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if want, have := [4]string{"1001", "1001", "1001", "0"}, s.GIDs; want != have {
-		t.Errorf("want uids %s, have %s", want, have)
+	if want, have := [4]uint64{1001, 1001, 1001, 0}, s.GIDs; want != have {
+		t.Errorf("want gids %v, have %v", want, have)
 	}
 }
 
diff --git a/sysfs/class_fibrechannel_test.go b/sysfs/class_fibrechannel_test.go
index 55f728c..5f1869e 100644
--- a/sysfs/class_fibrechannel_test.go
+++ b/sysfs/class_fibrechannel_test.go
@@ -33,55 +33,109 @@ func TestFibreChannelClass(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// "host0" FibreChannelHost constants.
+	var (
+		name0             = "host0"
+		speed0            = "16 Gbit"
+		portState0        = "Online"
+		portType0         = "Point-To-Point (direct nport connection)"
+		portName0         = "1000e0071bce95f2"
+		symbolicName0     = "Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux"
+		nodeName0         = "2000e0071bce95f2"
+		portID0           = "000002"
+		fabricName0       = "0"
+		devLossTMO0       = "30"
+		supportedClasses0 = "Class 3"
+		supportedSpeeds0  = "4 Gbit, 8 Gbit, 16 Gbit"
+
+		// "host0" FibreChannelHost.Counters constants.
+		dumpedFrames0          = ^uint64(0)
+		errorFrames0           = uint64(0)
+		invalidCRCCount0       = uint64(0x2)
+		rxFrames0              = uint64(0x3)
+		rxWords0               = uint64(0x4)
+		txFrames0              = uint64(0x5)
+		txWords0               = uint64(0x6)
+		secondsSinceLastReset0 = uint64(0x7)
+		invalidTXWordCount0    = uint64(0x8)
+		linkFailureCount0      = uint64(0x9)
+		lossOfSyncCount0       = uint64(0x10)
+		lossOfSignalCount0     = uint64(0x11)
+		nosCount0              = uint64(0x12)
+		fcpPacketAborts0       = uint64(0x13)
+	)
+
+	// "host1" FibreChannelHost constants.
+	var (
+		name1      = "host1"
+		portState1 = "Online"
+
+		// "host1" FibreChannelHost.Counters constants.
+		dumpedFrames1          = uint64(0)
+		errorFrames1           = ^uint64(0)
+		invalidCRCCount1       = uint64(0x20)
+		rxFrames1              = uint64(0x30)
+		rxWords1               = uint64(0x40)
+		txFrames1              = uint64(0x50)
+		txWords1               = uint64(0x60)
+		secondsSinceLastReset1 = uint64(0x70)
+		invalidTXWordCount1    = uint64(0x80)
+		linkFailureCount1      = uint64(0x90)
+		lossOfSyncCount1       = uint64(0x100)
+		lossOfSignalCount1     = uint64(0x110)
+		nosCount1              = uint64(0x120)
+		fcpPacketAborts1       = uint64(0x130)
+	)
+
 	want := FibreChannelClass{
 		"host0": FibreChannelHost{
-			Name:             "host0",
-			Speed:            "16 Gbit",
-			PortState:        "Online",
-			PortType:         "Point-To-Point (direct nport connection)",
-			PortName:         "1000e0071bce95f2",
-			SymbolicName:     "Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux",
-			NodeName:         "2000e0071bce95f2",
-			PortID:           "000002",
-			FabricName:       "0",
-			DevLossTMO:       "30",
-			SupportedClasses: "Class 3",
-			SupportedSpeeds:  "4 Gbit, 8 Gbit, 16 Gbit",
-			Counters: FibreChannelCounters{
-				DumpedFrames:          ^uint64(0),
-				ErrorFrames:           0,
-				InvalidCRCCount:       0x2,
-				RXFrames:              0x3,
-				RXWords:               0x4,
-				TXFrames:              0x5,
-				TXWords:               0x6,
-				SecondsSinceLastReset: 0x7,
-				InvalidTXWordCount:    0x8,
-				LinkFailureCount:      0x9,
-				LossOfSyncCount:       0x10,
-				LossOfSignalCount:     0x11,
-				NosCount:              0x12,
-				FCPPacketAborts:       0x13,
+			Name:             &name0,
+			Speed:            &speed0,
+			PortState:        &portState0,
+			PortType:         &portType0,
+			PortName:         &portName0,
+			SymbolicName:     &symbolicName0,
+			NodeName:         &nodeName0,
+			PortID:           &portID0,
+			FabricName:       &fabricName0,
+			DevLossTMO:       &devLossTMO0,
+			SupportedClasses: &supportedClasses0,
+			SupportedSpeeds:  &supportedSpeeds0,
+			Counters: &FibreChannelCounters{
+				DumpedFrames:          &dumpedFrames0,
+				ErrorFrames:           &errorFrames0,
+				InvalidCRCCount:       &invalidCRCCount0,
+				RXFrames:              &rxFrames0,
+				RXWords:               &rxWords0,
+				TXFrames:              &txFrames0,
+				TXWords:               &txWords0,
+				SecondsSinceLastReset: &secondsSinceLastReset0,
+				InvalidTXWordCount:    &invalidTXWordCount0,
+				LinkFailureCount:      &linkFailureCount0,
+				LossOfSyncCount:       &lossOfSyncCount0,
+				LossOfSignalCount:     &lossOfSignalCount0,
+				NosCount:              &nosCount0,
+				FCPPacketAborts:       &fcpPacketAborts0,
 			},
 		},
 		"host1": FibreChannelHost{
-			Name:      "host1",
-			PortState: "Online",
-			Counters: FibreChannelCounters{
-				DumpedFrames:          0,
-				ErrorFrames:           ^uint64(0),
-				InvalidCRCCount:       0x20,
-				RXFrames:              0x30,
-				RXWords:               0x40,
-				TXFrames:              0x50,
-				TXWords:               0x60,
-				SecondsSinceLastReset: 0x70,
-				InvalidTXWordCount:    0x80,
-				LinkFailureCount:      0x90,
-				LossOfSyncCount:       0x100,
-				LossOfSignalCount:     0x110,
-				NosCount:              0x120,
-				FCPPacketAborts:       0x130,
+			Name:      &name1,
+			PortState: &portState1,
+			Counters: &FibreChannelCounters{
+				DumpedFrames:          &dumpedFrames1,
+				ErrorFrames:           &errorFrames1,
+				InvalidCRCCount:       &invalidCRCCount1,
+				RXFrames:              &rxFrames1,
+				RXWords:               &rxWords1,
+				TXFrames:              &txFrames1,
+				TXWords:               &txWords1,
+				SecondsSinceLastReset: &secondsSinceLastReset1,
+				InvalidTXWordCount:    &invalidTXWordCount1,
+				LinkFailureCount:      &linkFailureCount1,
+				LossOfSyncCount:       &lossOfSyncCount1,
+				LossOfSignalCount:     &lossOfSignalCount1,
+				NosCount:              &nosCount1,
+				FCPPacketAborts:       &fcpPacketAborts1,
 			},
 		},
 	}
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index c5d9a6d..170a526 100644
--- a/testdata/fixtures.ttar
+++ b/testdata/fixtures.ttar
@@ -814,8 +814,8 @@ Ngid:   12345
 Pid:    26235
 PPid:   1234
 TracerPid:      0
-Uid:    0       0       0       0
-Gid:    0       0       0       0
+Uid:	0	0	0	0
+Gid:	0	0	0	0
 FDSize: 64
 Groups:
 NStgid: 26235   1