Repository: prometheus/procfs
Pull request #599 (2023-12-18 19:17:07)

Add support for SELinux AVC statistics
Add interfaces to read the SELinux AVC runtime statistics from /sys/fs/selinux/avc/cache_stats and /sys/fs/selinux/avc/hash_stats.

Refactored from https://github.com/prometheus/node_exporter/pull/2418

/cc @jpds


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index d882217..684eb15 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,9 @@ workflows:
         matrix:
           parameters:
             go_version:
-            - "1.19"
             - "1.20"
             - "1.21"
+            - "1.22"
     - test:
         name: test-windows
         os: windows
@@ -56,6 +56,6 @@ 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 4dc7b83..5ceb590 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -24,8 +24,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-      - name: install Go
+        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+      - name: Install Go
         uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
         with:
           go-version: 1.22.x
@@ -33,6 +33,7 @@ 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@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
         with:
-          version: v1.55.2
+          args: --verbose
+          version: v1.59.0
diff --git a/.golangci.yml b/.golangci.yml
index c24864a..d7476d5 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,9 +1,18 @@
 ---
 linters:
   enable:
+  - errcheck
   - godot
+  - gofmt
+  - goimports
+  - gosimple
+  - govet
+  - ineffassign
   - misspell
   - revive
+  - staticcheck
+  - testifylint
+  - unused
 
 linter-settings:
   godot:
@@ -11,5 +20,7 @@ linter-settings:
     exclude:
     # Ignore "See: URL"
     - 'See:'
+  goimports:
+      local-prefixes: github.com/prometheus/procfs
   misspell:
     locale: US
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 9255815..1617292 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -49,19 +49,19 @@ 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 :=
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.55.2
+GOLANGCI_LINT_VERSION ?= v1.59.0
 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
 # windows isn't included here because of the path separator being different.
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
@@ -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 .
@@ -208,6 +208,10 @@ common-tarball: promu
 	@echo ">> building release tarball"
 	$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
 
+.PHONY: common-docker-repo-name
+common-docker-repo-name:
+	@echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)"
+
 .PHONY: common-docker $(BUILD_DOCKER_ARCHS)
 common-docker: $(BUILD_DOCKER_ARCHS)
 $(BUILD_DOCKER_ARCHS): common-docker-%:
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..8380750 100644
--- a/buddyinfo.go
+++ b/buddyinfo.go
@@ -58,8 +58,8 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
 			return nil, fmt.Errorf("%w: Invalid number of fields, found: %v", ErrFileParse, parts)
 		}
 
-		node := strings.TrimRight(parts[1], ",")
-		zone := strings.TrimRight(parts[3], ",")
+		node := strings.TrimSuffix(parts[1], ",")
+		zone := strings.TrimSuffix(parts[3], ",")
 		arraySize := len(parts[4:])
 
 		if bucketCount == -1 {
@@ -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..6e9fd6f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,9 @@
 module github.com/prometheus/procfs
 
-go 1.19
+go 1.20
 
 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.20.0
 )
diff --git a/go.sum b/go.sum
index 037f53a..cc15e2f 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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 3c18c76..3a43e83 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -28,6 +28,9 @@ const (
 
 	// DefaultConfigfsMountPoint is the common mount point of the configfs.
 	DefaultConfigfsMountPoint = "/sys/kernel/config"
+
+	// DefaultSelinuxMountPoint is the common mount point of the selinuxfs.
+	DefaultSelinuxMountPoint = "/sys/fs/selinux"
 )
 
 // FS represents a pseudo-filesystem, normally /proc or /sys, which provides an
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..67a9d2b 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -23,7 +23,7 @@ import (
 
 var (
 	statusLineRE         = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[([U_]+)\]`)
-	recoveryLineBlocksRE = regexp.MustCompile(`\((\d+)/\d+\)`)
+	recoveryLineBlocksRE = regexp.MustCompile(`\((\d+/\d+)\)`)
 	recoveryLinePctRE    = regexp.MustCompile(`= (.+)%`)
 	recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`)
 	recoveryLineSpeedRE  = regexp.MustCompile(`speed=(.+)[A-Z]`)
@@ -50,6 +50,8 @@ type MDStat struct {
 	BlocksTotal int64
 	// Number of blocks on the device that are in sync.
 	BlocksSynced int64
+	// Number of blocks on the device that need to be synced.
+	BlocksToBeSynced int64
 	// progress percentage of current sync
 	BlocksSyncedPct float64
 	// estimated finishing time for current sync (in minutes)
@@ -70,7 +72,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 +92,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 +107,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
@@ -115,7 +117,8 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 
 		// If device is syncing at the moment, get the number of currently
 		// synced bytes, otherwise that number equals the size of the device.
-		syncedBlocks := size
+		blocksSynced := size
+		blocksToBeSynced := size
 		speed := float64(0)
 		finish := float64(0)
 		pct := float64(0)
@@ -136,11 +139,11 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			// Handle case when resync=PENDING or resync=DELAYED.
 			if strings.Contains(lines[syncLineIdx], "PENDING") ||
 				strings.Contains(lines[syncLineIdx], "DELAYED") {
-				syncedBlocks = 0
+				blocksSynced = 0
 			} else {
-				syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
+				blocksSynced, blocksToBeSynced, 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)
 				}
 			}
 		}
@@ -154,7 +157,8 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			DisksSpare:             spare,
 			DisksTotal:             total,
 			BlocksTotal:            size,
-			BlocksSynced:           syncedBlocks,
+			BlocksSynced:           blocksSynced,
+			BlocksToBeSynced:       blocksToBeSynced,
 			BlocksSyncedPct:        pct,
 			BlocksSyncedFinishTime: finish,
 			BlocksSyncedSpeed:      speed,
@@ -168,13 +172,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,65 +193,71 @@ 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], "_"))
 
 	return active, total, down, size, nil
 }
 
-func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
+func evalRecoveryLine(recoveryLine string) (blocksSynced int64, blocksToBeSynced 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, 0, fmt.Errorf("%w: Unexpected recoveryLine blocks %s: %w", ErrFileParse, recoveryLine, err)
 	}
 
-	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
+	blocks := strings.Split(matches[1], "/")
+	blocksSynced, err = strconv.ParseInt(blocks[0], 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, 0, fmt.Errorf("%w: Unable to parse recovery blocks synced %q: %w", ErrFileParse, matches[1], err)
+	}
+
+	blocksToBeSynced, err = strconv.ParseInt(blocks[1], 10, 64)
+	if err != nil {
+		return blocksSynced, 0, 0, 0, 0, fmt.Errorf("%w: Unable to parse recovery to be synced blocks %q: %w", ErrFileParse, matches[2], err)
 	}
 
 	// Get percentage complete
 	matches = recoveryLinePctRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching percentage %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching percentage %s", ErrFileParse, recoveryLine)
 	}
 	pct, err = strconv.ParseFloat(strings.TrimSpace(matches[1]), 64)
 	if err != nil {
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Error parsing float from recoveryLine %q", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, 0, 0, 0, fmt.Errorf("%w: Error parsing float from recoveryLine %q", ErrFileParse, recoveryLine)
 	}
 
 	// Get time expected left to complete
 	matches = recoveryLineFinishRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching est. finish time: %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching est. finish time: %s", ErrFileParse, recoveryLine)
 	}
 	finish, err = strconv.ParseFloat(matches[1], 64)
 	if err != nil {
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unable to parse float from recoveryLine: %q", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, 0, 0, fmt.Errorf("%w: Unable to parse float from recoveryLine: %q", ErrFileParse, recoveryLine)
 	}
 
 	// Get recovery speed
 	matches = recoveryLineSpeedRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("%w: Unexpected recoveryLine value: %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, finish, 0, fmt.Errorf("%w: Unexpected recoveryLine value: %s", ErrFileParse, recoveryLine)
 	}
 	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 blocksSynced, blocksToBeSynced, pct, finish, 0, fmt.Errorf("%w: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
 	}
 
-	return syncedBlocks, pct, finish, speed, nil
+	return blocksSynced, blocksToBeSynced, pct, finish, speed, nil
 }
 
 func evalComponentDevices(deviceFields []string) []string {
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_tcp.go b/net_tcp.go
index 5277629..0396d72 100644
--- a/net_tcp.go
+++ b/net_tcp.go
@@ -25,24 +25,28 @@ type (
 
 // NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams
 // read from /proc/net/tcp.
+// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
 func (fs FS) NetTCP() (NetTCP, error) {
 	return newNetTCP(fs.proc.Path("net/tcp"))
 }
 
 // NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams
 // read from /proc/net/tcp6.
+// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
 func (fs FS) NetTCP6() (NetTCP, error) {
 	return newNetTCP(fs.proc.Path("net/tcp6"))
 }
 
 // NetTCPSummary returns already computed statistics like the total queue lengths
 // for TCP datagrams read from /proc/net/tcp.
+// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
 func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
 	return newNetTCPSummary(fs.proc.Path("net/tcp"))
 }
 
 // NetTCP6Summary returns already computed statistics like the total queue lengths
 // for TCP datagrams read from /proc/net/tcp6.
+// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
 func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) {
 	return newNetTCPSummary(fs.proc.Path("net/tcp6"))
 }
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..1427963 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{}
@@ -137,7 +137,7 @@ func (p Proc) CmdLine() ([]string, error) {
 		return []string{}, nil
 	}
 
-	return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
+	return strings.Split(string(bytes.TrimRight(data, "\x00")), "\x00"), nil
 }
 
 // Wchan returns the wchan (wait channel) of a process.
@@ -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_smaps.go b/proc_smaps.go
index ad8785a..09060e8 100644
--- a/proc_smaps.go
+++ b/proc_smaps.go
@@ -127,7 +127,7 @@ func (s *ProcSMapsRollup) parseLine(line string) error {
 	}
 
 	v := strings.TrimSpace(kv[1])
-	v = strings.TrimRight(v, " kB")
+	v = strings.TrimSuffix(v, " kB")
 
 	vKBytes, err := strconv.ParseUint(v, 10, 64)
 	if err != nil {
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/selinuxfs/avc_cache_stats.go b/selinuxfs/avc_cache_stats.go
new file mode 100644
index 0000000..0c37e0f
--- /dev/null
+++ b/selinuxfs/avc_cache_stats.go
@@ -0,0 +1,104 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// SELinux access vector cache statistics.
+type AVCStat struct {
+	// Number of total lookups
+	Lookups uint64
+	// Number of total hits
+	Hits uint64
+	// Number of total misses
+	Misses uint64
+	// Number of total allocations
+	Allocations uint64
+	// Number of total reclaims
+	Reclaims uint64
+	// Number of total frees
+	Frees uint64
+}
+
+// ParseAVCStats returns the total SELinux access vector cache statistics,
+// or error on failure.
+func (fs FS) ParseAVCStats() (AVCStat, error) {
+	avcStat := AVCStat{}
+
+	file, err := os.Open(fs.selinux.Path("avc/cache_stats"))
+	if err != nil {
+		return avcStat, err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	scanner.Scan() // Skip header
+
+	for scanner.Scan() {
+		avcValues := strings.Fields(scanner.Text())
+
+		if len(avcValues) != 6 {
+			return avcStat, fmt.Errorf("invalid AVC stat line: %s",
+				scanner.Text())
+		}
+
+		lookups, err := strconv.ParseUint(avcValues[0], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for lookups")
+		}
+
+		hits, err := strconv.ParseUint(avcValues[1], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for hits")
+		}
+
+		misses, err := strconv.ParseUint(avcValues[2], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for misses")
+		}
+
+		allocations, err := strconv.ParseUint(avcValues[3], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for allocations")
+		}
+
+		reclaims, err := strconv.ParseUint(avcValues[4], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for reclaims")
+		}
+
+		frees, err := strconv.ParseUint(avcValues[5], 0, 64)
+		if err != nil {
+			return avcStat, fmt.Errorf("could not parse expected integer value for frees")
+		}
+
+		avcStat.Lookups += lookups
+		avcStat.Hits += hits
+		avcStat.Misses += misses
+		avcStat.Allocations += allocations
+		avcStat.Reclaims += reclaims
+		avcStat.Frees += frees
+	}
+
+	return avcStat, scanner.Err()
+}
diff --git a/selinuxfs/avc_hash_stats.go b/selinuxfs/avc_hash_stats.go
new file mode 100644
index 0000000..1d01755
--- /dev/null
+++ b/selinuxfs/avc_hash_stats.go
@@ -0,0 +1,83 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// SELinux access vector cache hashtable statistics.
+type AVCHashStat struct {
+	// Number of entries
+	Entries uint64
+	// Number of buckets used
+	BucketsUsed uint64
+	// Number of buckets available
+	BucketsAvailable uint64
+	// Length of the longest chain
+	LongestChain uint64
+}
+
+// ParseAVCHashStats returns the SELinux access vector cache hashtable
+// statistics, or error on failure.
+func (fs FS) ParseAVCHashStats() (AVCHashStat, error) {
+	avcHashStat := AVCHashStat{}
+
+	file, err := os.Open(fs.selinux.Path("avc/hash_stats"))
+	if err != nil {
+		return avcHashStat, err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+
+	scanner.Scan()
+	entriesValue := strings.TrimPrefix(scanner.Text(), "entries: ")
+
+	scanner.Scan()
+	bucketsValues := strings.Split(scanner.Text(), "buckets used: ")
+	bucketsValuesTuple := strings.Split(bucketsValues[1], "/")
+
+	scanner.Scan()
+	longestChainValue := strings.TrimPrefix(scanner.Text(), "longest chain: ")
+
+	avcHashStat.Entries, err = strconv.ParseUint(entriesValue, 0, 64)
+	if err != nil {
+		return avcHashStat, fmt.Errorf("could not parse expected integer value for hash entries")
+	}
+
+	avcHashStat.BucketsUsed, err = strconv.ParseUint(bucketsValuesTuple[0], 0, 64)
+	if err != nil {
+		return avcHashStat, fmt.Errorf("could not parse expected integer value for hash buckets used")
+	}
+
+	avcHashStat.BucketsAvailable, err = strconv.ParseUint(bucketsValuesTuple[1], 0, 64)
+	if err != nil {
+		return avcHashStat, fmt.Errorf("could not parse expected integer value for hash buckets available")
+	}
+
+	avcHashStat.LongestChain, err = strconv.ParseUint(longestChainValue, 0, 64)
+	if err != nil {
+		return avcHashStat, fmt.Errorf("could not parse expected integer value for hash longest chain")
+	}
+
+	return avcHashStat, scanner.Err()
+}
diff --git a/selinuxfs/fs.go b/selinuxfs/fs.go
new file mode 100644
index 0000000..f3c7137
--- /dev/null
+++ b/selinuxfs/fs.go
@@ -0,0 +1,46 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import (
+	"github.com/prometheus/procfs/internal/fs"
+)
+
+// FS represents the pseudo-filesystem selinixfs, which provides an interface to
+// SELinux data structures.
+type FS struct {
+	selinux fs.FS
+}
+
+// DefaultMountPoint is the common mount point of the selinuxfs filesystem.
+const DefaultMountPoint = fs.DefaultSelinuxMountPoint
+
+// NewDefaultFS returns a new FS mounted under the default mountPoint. It will error
+// if the mount point can't be read.
+func NewDefaultFS() (FS, error) {
+	return NewFS(DefaultMountPoint)
+}
+
+// NewFS returns a new FS mounted under the given mountPoint. It will error
+// if the mount point can't be read.
+func NewFS(mountPoint string) (FS, error) {
+	fs, err := fs.NewFS(mountPoint)
+	if err != nil {
+		return FS{}, err
+	}
+	return FS{fs}, 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/sysfs/class_power_supply.go b/sysfs/class_power_supply.go
index c50c32b..cb1a251 100644
--- a/sysfs/class_power_supply.go
+++ b/sysfs/class_power_supply.go
@@ -143,7 +143,7 @@ func parsePowerSupply(path string) (*PowerSupply, error) {
 		name := filepath.Join(path, f.Name())
 		value, err := util.SysReadFile(name)
 		if err != nil {
-			if os.IsNotExist(err) || err.Error() == "operation not supported" || errors.Is(err, os.ErrInvalid) {
+			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "no such device" || errors.Is(err, os.ErrInvalid) {
 				continue
 			}
 			return nil, fmt.Errorf("failed to read file %q: %w", name, err)
diff --git a/sysfs/class_thermal.go b/sysfs/class_thermal.go
index 0935a21..10f4a9a 100644
--- a/sysfs/class_thermal.go
+++ b/sysfs/class_thermal.go
@@ -23,6 +23,8 @@ import (
 	"strings"
 	"syscall"
 
+	fsp "io/fs"
+
 	"github.com/prometheus/procfs/internal/util"
 )
 
@@ -49,7 +51,7 @@ func (fs FS) ClassThermalZoneStats() ([]ClassThermalZoneStats, error) {
 	for _, zone := range zones {
 		zoneStats, err := parseClassThermalZone(zone)
 		if err != nil {
-			if errors.Is(err, syscall.ENODATA) {
+			if errors.Is(err, syscall.ENODATA) || errors.As(err, new(*fsp.PathError)) {
 				continue
 			}
 			return nil, err
diff --git a/sysfs/system_cpu.go b/sysfs/system_cpu.go
index fbecc19..a68c6d9 100644
--- a/sysfs/system_cpu.go
+++ b/sysfs/system_cpu.go
@@ -65,6 +65,10 @@ type SystemCPUCpufreqStats struct {
 	Governor                 string
 	RelatedCpus              string
 	SetSpeed                 string
+	// Refer `CONFIG_CPU_FREQ_STAT`: https://www.kernel.org/doc/html/latest/cpu-freq/cpufreq-stats.html#configuring-cpufreq-stats
+	CpuinfoFrequencyDuration         *map[uint64]uint64
+	CpuinfoFrequencyTransitionsTotal *uint64
+	CpuinfoTransitionTable           *[][]uint64
 }
 
 // CPUs returns a slice of all CPUs in `/sys/devices/system/cpu`.
@@ -128,6 +132,16 @@ func (c CPU) ThermalThrottle() (*CPUThermalThrottle, error) {
 	return t, nil
 }
 
+// Online returns the online status of a CPU from `/sys/devices/system/cpu/cpuN/online`.
+func (c CPU) Online() (bool, error) {
+	cpuPath := filepath.Join(string(c), "online")
+	str, err := util.SysReadFile(cpuPath)
+	if err != nil {
+		return false, err
+	}
+	return str == "1", nil
+}
+
 func parseCPUThermalThrottle(cpuPath string) (*CPUThermalThrottle, error) {
 	t := CPUThermalThrottle{}
 	var err error
@@ -292,19 +306,93 @@ func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
 		}
 	}
 
+	// "total_trans" is the total number of times the CPU has changed frequency.
+	var cpuinfoFrequencyTransitionsTotal *uint64
+	cpuinfoFrequencyTransitionsTotalUint, err := util.ReadUintFromFile(filepath.Join(cpuPath, "stats", "total_trans"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoFrequencyTransitionsTotal = &cpuinfoFrequencyTransitionsTotalUint
+	}
+
+	// "time_in_state" is the total time spent at each frequency.
+	var cpuinfoFrequencyDuration *map[uint64]uint64
+	cpuinfoFrequencyDurationString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "time_in_state"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoFrequencyDuration = &map[uint64]uint64{}
+		for _, line := range strings.Split(string(cpuinfoFrequencyDurationString), "\n") {
+			if line == "" {
+				continue
+			}
+			fields := strings.Fields(line)
+			if len(fields) != 2 {
+				return &SystemCPUCpufreqStats{}, fmt.Errorf("unexpected number of fields in time_in_state: %v", fields)
+			}
+			freq, err := strconv.ParseUint(fields[0], 10, 64)
+			if err != nil {
+				return &SystemCPUCpufreqStats{}, err
+			}
+			duration, err := strconv.ParseUint(fields[1], 10, 64)
+			if err != nil {
+				return &SystemCPUCpufreqStats{}, err
+			}
+			(*cpuinfoFrequencyDuration)[freq] = duration
+		}
+	}
+
+	// "trans_table" contains information about all the CPU frequency transitions.
+	var cpuinfoTransitionTable *[][]uint64
+	cpuinfoTransitionTableString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "trans_table"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoTransitionTable = &[][]uint64{}
+		for i, line := range strings.Split(string(cpuinfoTransitionTableString), "\n") {
+			// Skip the "From: To" header.
+			if i == 0 || line == "" {
+				continue
+			}
+			fields := strings.Fields(line)
+			fields[0] = strings.TrimSuffix(fields[0], ":")
+			cpuinfoTransitionTableRow := make([]uint64, len(fields))
+			for i := range fields {
+				if len(fields[i]) == 0 {
+					continue
+				}
+				f, err := strconv.ParseUint(fields[i], 10, 64)
+				if err != nil {
+					return &SystemCPUCpufreqStats{}, err
+				}
+				cpuinfoTransitionTableRow[i] = f
+			}
+			*cpuinfoTransitionTable = append(*cpuinfoTransitionTable, cpuinfoTransitionTableRow)
+		}
+	}
+
 	return &SystemCPUCpufreqStats{
-		CpuinfoCurrentFrequency:  uintOut[0],
-		CpuinfoMaximumFrequency:  uintOut[1],
-		CpuinfoMinimumFrequency:  uintOut[2],
-		CpuinfoTransitionLatency: uintOut[3],
-		ScalingCurrentFrequency:  uintOut[4],
-		ScalingMaximumFrequency:  uintOut[5],
-		ScalingMinimumFrequency:  uintOut[6],
-		AvailableGovernors:       stringOut[0],
-		Driver:                   stringOut[1],
-		Governor:                 stringOut[2],
-		RelatedCpus:              stringOut[3],
-		SetSpeed:                 stringOut[4],
+		CpuinfoCurrentFrequency:          uintOut[0],
+		CpuinfoMaximumFrequency:          uintOut[1],
+		CpuinfoMinimumFrequency:          uintOut[2],
+		CpuinfoTransitionLatency:         uintOut[3],
+		ScalingCurrentFrequency:          uintOut[4],
+		ScalingMaximumFrequency:          uintOut[5],
+		ScalingMinimumFrequency:          uintOut[6],
+		AvailableGovernors:               stringOut[0],
+		Driver:                           stringOut[1],
+		Governor:                         stringOut[2],
+		RelatedCpus:                      stringOut[3],
+		SetSpeed:                         stringOut[4],
+		CpuinfoFrequencyDuration:         cpuinfoFrequencyDuration,
+		CpuinfoFrequencyTransitionsTotal: cpuinfoFrequencyTransitionsTotal,
+		CpuinfoTransitionTable:           cpuinfoTransitionTable,
 	}, nil
 }
 
@@ -321,7 +409,7 @@ func parseCPURange(data []byte) ([]uint16, error) {
 
 	var cpusInt = []uint16{}
 
-	for _, cpu := range strings.Split(strings.TrimRight(string(data), "\n"), ",") {
+	for _, cpu := range strings.Split(strings.TrimSuffix(string(data), "\n"), ",") {
 		if cpu == "" {
 			continue
 		}
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/mdstat_test.go b/mdstat_test.go
index 871e95a..7a577ed 100644
--- a/mdstat_test.go
+++ b/mdstat_test.go
@@ -13,8 +13,11 @@
 
 package procfs
 
-import "testing"
-import "github.com/google/go-cmp/cmp"
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
 
 func TestFS_MDStat(t *testing.T) {
 	fs := getProcFixtures(t)
@@ -25,23 +28,261 @@ func TestFS_MDStat(t *testing.T) {
 	}
 
 	refs := map[string]MDStat{
-		"md127": {Name: "md127", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 312319552, BlocksSynced: 312319552, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdi2", "sdj2"}},
-		"md0":   {Name: "md0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 248896, BlocksSynced: 248896, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdi1", "sdj1"}},
-		"md4":   {Name: "md4", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, DisksFailed: 1, DisksDown: 0, DisksSpare: 1, BlocksTotal: 4883648, BlocksSynced: 4883648, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda3", "sdb3"}},
-		"md6":   {Name: "md6", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, DisksFailed: 1, DisksDown: 1, DisksSpare: 1, BlocksTotal: 195310144, BlocksSynced: 16775552, BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, Devices: []string{"sdb2", "sdc", "sda2"}},
-		"md3":   {Name: "md3", ActivityState: "active", DisksActive: 8, DisksTotal: 8, DisksFailed: 0, DisksDown: 0, DisksSpare: 2, BlocksTotal: 5853468288, BlocksSynced: 5853468288, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}},
-		"md8":   {Name: "md8", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 2, BlocksTotal: 195310144, BlocksSynced: 16775552, BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, Devices: []string{"sdb1", "sda1", "sdc", "sde"}},
-		"md7":   {Name: "md7", ActivityState: "active", DisksActive: 3, DisksTotal: 4, DisksFailed: 1, DisksDown: 1, DisksSpare: 0, BlocksTotal: 7813735424, BlocksSynced: 7813735424, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb1", "sde1", "sdd1", "sdc1"}},
-		"md9":   {Name: "md9", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, DisksSpare: 1, DisksDown: 0, DisksFailed: 2, BlocksTotal: 523968, BlocksSynced: 0, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}},
-		"md10":  {Name: "md10", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 314159265, BlocksSynced: 314159265, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdb1"}},
-		"md11":  {Name: "md11", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 1, DisksDown: 0, DisksSpare: 2, BlocksTotal: 4190208, BlocksSynced: 0, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}},
-		"md12":  {Name: "md12", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksSpare: 0, DisksDown: 0, DisksFailed: 0, BlocksTotal: 3886394368, BlocksSynced: 3886394368, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc2", "sdd2"}},
-		"md120": {Name: "md120", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 2095104, BlocksSynced: 2095104, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdb1"}},
-		"md126": {Name: "md126", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 1855870976, BlocksSynced: 1855870976, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb", "sdc"}},
-		"md219": {Name: "md219", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, DisksActive: 0, DisksDown: 0, DisksSpare: 3, BlocksTotal: 7932, BlocksSynced: 7932, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc", "sda"}},
-		"md00":  {Name: "md00", ActivityState: "active", DisksActive: 1, DisksTotal: 1, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 4186624, BlocksSynced: 4186624, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"xvdb"}},
-		"md101": {Name: "md101", ActivityState: "active", DisksActive: 3, DisksTotal: 3, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 322560, BlocksSynced: 322560, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb", "sdd", "sdc"}},
-		"md201": {Name: "md201", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 1993728, BlocksSynced: 114176, BlocksSyncedPct: 5.7, BlocksSyncedFinishTime: 0.2, BlocksSyncedSpeed: 114176, Devices: []string{"sda3", "sdb3"}},
+		"md127": {
+			Name:                   "md127",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            312319552,
+			BlocksSynced:           312319552,
+			BlocksToBeSynced:       312319552,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdi2", "sdj2"}},
+		"md0": {
+			Name:                   "md0",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            248896,
+			BlocksSynced:           248896,
+			BlocksToBeSynced:       248896,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdi1", "sdj1"}},
+		"md4": {
+			Name:                   "md4",
+			ActivityState:          "inactive",
+			DisksActive:            0,
+			DisksTotal:             0,
+			DisksFailed:            1,
+			DisksDown:              0,
+			DisksSpare:             1,
+			BlocksTotal:            4883648,
+			BlocksSynced:           4883648,
+			BlocksToBeSynced:       4883648,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda3", "sdb3"}},
+		"md6": {
+			Name:                   "md6",
+			ActivityState:          "recovering",
+			DisksActive:            1,
+			DisksTotal:             2,
+			DisksFailed:            1,
+			DisksDown:              1,
+			DisksSpare:             1,
+			BlocksTotal:            195310144,
+			BlocksSynced:           16775552,
+			BlocksToBeSynced:       195310144,
+			BlocksSyncedPct:        8.5,
+			BlocksSyncedFinishTime: 17,
+			BlocksSyncedSpeed:      259783,
+			Devices:                []string{"sdb2", "sdc", "sda2"}},
+		"md3": {
+			Name:                   "md3",
+			ActivityState:          "active",
+			DisksActive:            8,
+			DisksTotal:             8,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            5853468288,
+			BlocksSynced:           5853468288,
+			BlocksToBeSynced:       5853468288,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}},
+		"md8": {
+			Name:                   "md8",
+			ActivityState:          "resyncing",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            195310144,
+			BlocksSynced:           16775552,
+			BlocksToBeSynced:       195310144,
+			BlocksSyncedPct:        8.5,
+			BlocksSyncedFinishTime: 17,
+			BlocksSyncedSpeed:      259783,
+			Devices:                []string{"sdb1", "sda1", "sdc", "sde"}},
+		"md7": {
+			Name:                   "md7",
+			ActivityState:          "active",
+			DisksActive:            3,
+			DisksTotal:             4,
+			DisksFailed:            1,
+			DisksDown:              1,
+			DisksSpare:             0,
+			BlocksTotal:            7813735424,
+			BlocksSynced:           7813735424,
+			BlocksToBeSynced:       7813735424,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb1", "sde1", "sdd1", "sdc1"}},
+		"md9": {
+			Name:                   "md9",
+			ActivityState:          "resyncing",
+			DisksActive:            4,
+			DisksTotal:             4,
+			DisksSpare:             1,
+			DisksDown:              0,
+			DisksFailed:            2,
+			BlocksTotal:            523968,
+			BlocksSynced:           0,
+			BlocksToBeSynced:       523968,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}},
+		"md10": {
+			Name:                   "md10",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            314159265,
+			BlocksSynced:           314159265,
+			BlocksToBeSynced:       314159265,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdb1"}},
+		"md11": {
+			Name:                   "md11",
+			ActivityState:          "resyncing",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            1,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            4190208,
+			BlocksSynced:           0,
+			BlocksToBeSynced:       4190208,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}},
+		"md12": {
+			Name:                   "md12",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksSpare:             0,
+			DisksDown:              0,
+			DisksFailed:            0,
+			BlocksTotal:            3886394368,
+			BlocksSynced:           3886394368,
+			BlocksToBeSynced:       3886394368,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc2", "sdd2"}},
+		"md120": {
+			Name:                   "md120",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            2095104,
+			BlocksSynced:           2095104,
+			BlocksToBeSynced:       2095104,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdb1"}},
+		"md126": {
+			Name:                   "md126",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            1855870976,
+			BlocksSynced:           1855870976,
+			BlocksToBeSynced:       1855870976,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb", "sdc"}},
+		"md219": {
+			Name:                   "md219",
+			ActivityState:          "inactive",
+			DisksTotal:             0,
+			DisksFailed:            0,
+			DisksActive:            0,
+			DisksDown:              0,
+			DisksSpare:             3,
+			BlocksTotal:            7932,
+			BlocksSynced:           7932,
+			BlocksToBeSynced:       7932,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc", "sda"}},
+		"md00": {
+			Name:                   "md00",
+			ActivityState:          "active",
+			DisksActive:            1,
+			DisksTotal:             1,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            4186624,
+			BlocksSynced:           4186624,
+			BlocksToBeSynced:       4186624,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"xvdb"}},
+		"md101": {
+			Name:                   "md101",
+			ActivityState:          "active",
+			DisksActive:            3,
+			DisksTotal:             3,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            322560,
+			BlocksSynced:           322560,
+			BlocksToBeSynced:       322560,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb", "sdd", "sdc"}},
+		"md201": {
+			Name:                   "md201",
+			ActivityState:          "checking",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            1993728,
+			BlocksSynced:           114176,
+			BlocksToBeSynced:       1993728,
+			BlocksSyncedPct:        5.7,
+			BlocksSyncedFinishTime: 0.2,
+			BlocksSyncedSpeed:      114176,
+			Devices:                []string{"sda3", "sdb3"}},
 	}
 
 	if want, have := len(refs), len(mdStats); want != have {
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/net_conntrackstat_test.go b/net_conntrackstat_test.go
index b19222a..67aa18d 100644
--- a/net_conntrackstat_test.go
+++ b/net_conntrackstat_test.go
@@ -34,7 +34,7 @@ func TestParseConntrackStat(t *testing.T) {
 	}
 
 	want := []ConntrackStatEntry{
-		ConntrackStatEntry{
+		{
 			Entries:       33,
 			Found:         0,
 			Invalid:       3,
@@ -45,7 +45,7 @@ func TestParseConntrackStat(t *testing.T) {
 			EarlyDrop:     0,
 			SearchRestart: 0,
 		},
-		ConntrackStatEntry{
+		{
 			Entries:       33,
 			Found:         0,
 			Invalid:       2,
@@ -56,7 +56,7 @@ func TestParseConntrackStat(t *testing.T) {
 			EarlyDrop:     0,
 			SearchRestart: 2,
 		},
-		ConntrackStatEntry{
+		{
 			Entries:       33,
 			Found:         0,
 			Invalid:       1,
@@ -67,7 +67,7 @@ func TestParseConntrackStat(t *testing.T) {
 			EarlyDrop:     0,
 			SearchRestart: 1,
 		},
-		ConntrackStatEntry{
+		{
 			Entries:       33,
 			Found:         0,
 			Invalid:       47,
@@ -96,7 +96,7 @@ func TestParseOldConntrackStat(t *testing.T) {
 	}
 
 	want := []ConntrackStatEntry{
-		ConntrackStatEntry{
+		{
 			Entries:       43,
 			Searched:      202143,
 			Found:         48658538,
diff --git a/net_udp_test.go b/net_udp_test.go
index 6576161..0db53bf 100644
--- a/net_udp_test.go
+++ b/net_udp_test.go
@@ -30,7 +30,7 @@ func Test_newNetUDP(t *testing.T) {
 			name: "udp file found, no error should come up",
 			file: "testdata/fixtures/proc/net/udp",
 			want: []*netIPSocketLine{
-				&netIPSocketLine{
+				{
 					Sl:        0,
 					LocalAddr: net.IP{10, 0, 0, 5},
 					LocalPort: 22,
@@ -43,7 +43,7 @@ func Test_newNetUDP(t *testing.T) {
 					Inode:     2740,
 					Drops:     intToU64(100),
 				},
-				&netIPSocketLine{
+				{
 					Sl:        1,
 					LocalAddr: net.IP{0, 0, 0, 0},
 					LocalPort: 22,
@@ -56,7 +56,7 @@ func Test_newNetUDP(t *testing.T) {
 					Inode:     2740,
 					Drops:     intToU64(100),
 				},
-				&netIPSocketLine{
+				{
 					Sl:        2,
 					LocalAddr: net.IP{0, 0, 0, 0},
 					LocalPort: 22,
@@ -76,7 +76,7 @@ func Test_newNetUDP(t *testing.T) {
 			name: "udp6 file found, no error should come up",
 			file: "testdata/fixtures/proc/net/udp6",
 			want: []*netIPSocketLine{
-				&netIPSocketLine{
+				{
 					Sl:        1315,
 					LocalAddr: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 					LocalPort: 5355,
@@ -89,7 +89,7 @@ func Test_newNetUDP(t *testing.T) {
 					Inode:     21040,
 					Drops:     intToU64(0),
 				},
-				&netIPSocketLine{
+				{
 					Sl:        6073,
 					LocalAddr: net.IP{254, 128, 0, 0, 0, 0, 0, 0, 86, 225, 173, 255, 254, 124, 102, 9},
 					LocalPort: 51073,
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/proc_test.go b/proc_test.go
index 239e35b..ecf889a 100644
--- a/proc_test.go
+++ b/proc_test.go
@@ -257,9 +257,9 @@ func TestFileDescriptorsInfo(t *testing.T) {
 	sort.Sort(fdinfos)
 	var want = ProcFDInfos{
 		ProcFDInfo{FD: "0", Pos: "0", Flags: "02004000", MntID: "13", InotifyInfos: []InotifyInfo{
-			InotifyInfo{WD: "3", Ino: "1", Sdev: "34", Mask: "fce"},
-			InotifyInfo{WD: "2", Ino: "1300016", Sdev: "fd00002", Mask: "fce"},
-			InotifyInfo{WD: "1", Ino: "2e0001", Sdev: "fd00000", Mask: "fce"},
+			{WD: "3", Ino: "1", Sdev: "34", Mask: "fce"},
+			{WD: "2", Ino: "1300016", Sdev: "fd00002", Mask: "fce"},
+			{WD: "1", Ino: "2e0001", Sdev: "fd00000", Mask: "fce"},
 		}},
 		ProcFDInfo{FD: "1", Pos: "0", Flags: "02004002", MntID: "13", InotifyInfos: nil},
 		ProcFDInfo{FD: "10", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
diff --git a/selinuxfs/avc_cache_stats_test.go b/selinuxfs/avc_cache_stats_test.go
new file mode 100644
index 0000000..6256088
--- /dev/null
+++ b/selinuxfs/avc_cache_stats_test.go
@@ -0,0 +1,57 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import (
+	"testing"
+)
+
+func TestAVCStats(t *testing.T) {
+	fs, err := NewFS(selinuxTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	avcStats, err := fs.ParseAVCStats()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if want, got := uint64(91590784), avcStats.Lookups; want != got {
+		t.Errorf("want avcstat lookups %v, got %v", want, got)
+	}
+
+	if want, got := uint64(91569452), avcStats.Hits; want != got {
+		t.Errorf("want avcstat hits %v, got %v", want, got)
+	}
+
+	if want, got := uint64(21332), avcStats.Misses; want != got {
+		t.Errorf("want avcstat misses %v, got %v", want, got)
+	}
+
+	if want, got := uint64(21332), avcStats.Allocations; want != got {
+		t.Errorf("want avcstat allocations %v, got %v", want, got)
+	}
+
+	if want, got := uint64(20400), avcStats.Reclaims; want != got {
+		t.Errorf("want avcstat reclaims %v, got %v", want, got)
+	}
+
+	if want, got := uint64(20826), avcStats.Frees; want != got {
+		t.Errorf("want avcstat frees %v, got %v", want, got)
+	}
+}
diff --git a/selinuxfs/avc_hash_stats_test.go b/selinuxfs/avc_hash_stats_test.go
new file mode 100644
index 0000000..46dd59c
--- /dev/null
+++ b/selinuxfs/avc_hash_stats_test.go
@@ -0,0 +1,49 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import (
+	"testing"
+)
+
+func TestAVCHashStat(t *testing.T) {
+	fs, err := NewFS(selinuxTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	avcHashStats, err := fs.ParseAVCHashStats()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if want, got := uint64(503), avcHashStats.Entries; want != got {
+		t.Errorf("want avc hash stat entries %v, got %v", want, got)
+	}
+
+	if want, got := uint64(512), avcHashStats.BucketsAvailable; want != got {
+		t.Errorf("want avc hash stat buckets available %v, got %v", want, got)
+	}
+
+	if want, got := uint64(257), avcHashStats.BucketsUsed; want != got {
+		t.Errorf("want avc hash stat buckets used %v, got %v", want, got)
+	}
+
+	if want, got := uint64(8), avcHashStats.LongestChain; want != got {
+		t.Errorf("want avc hash stat longest chain %v, got %v", want, got)
+	}
+}
diff --git a/selinuxfs/fs_test.go b/selinuxfs/fs_test.go
new file mode 100644
index 0000000..64b60f6
--- /dev/null
+++ b/selinuxfs/fs_test.go
@@ -0,0 +1,37 @@
+// Copyright 2023 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.
+
+//go:build linux && !noselinux
+// +build linux,!noselinux
+
+package selinuxfs
+
+import "testing"
+
+const (
+	selinuxTestFixtures = "testdata/fixtures" + DefaultMountPoint
+)
+
+func TestNewFS(t *testing.T) {
+	if _, err := NewFS("foobar"); err == nil {
+		t.Error("want NewFS to fail for non-existing mount point")
+	}
+
+	if _, err := NewFS("doc.go"); err == nil {
+		t.Error("want NewFS to fail if mount point is not a directory")
+	}
+
+	if _, err := NewFS(selinuxTestFixtures); err != nil {
+		t.Error("want NewFS to succeed if mount point exists")
+	}
+}
diff --git a/selinuxfs/testdata/fixtures b/selinuxfs/testdata/fixtures
new file mode 120000
index 0000000..5fdc97b
--- /dev/null
+++ b/selinuxfs/testdata/fixtures
@@ -0,0 +1,1 @@
+../../testdata/fixtures
\ No newline at end of file
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/sysfs/system_cpu_test.go b/sysfs/system_cpu_test.go
index 9fee2a1..a39c570 100644
--- a/sysfs/system_cpu_test.go
+++ b/sysfs/system_cpu_test.go
@@ -18,6 +18,7 @@ package sysfs
 
 import (
 	"errors"
+	"os"
 	"reflect"
 	"testing"
 )
@@ -63,6 +64,34 @@ func TestCPUTopology(t *testing.T) {
 	}
 }
 
+func TestCPUOnline(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cpus, err := fs.CPUs()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want, have := 3, len(cpus); want != have {
+		t.Errorf("incorrect number of CPUs, have %v, want %v", want, have)
+	}
+	cpu0Online, err := cpus[0].Online()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want, have := true, cpu0Online; want != have {
+		t.Errorf("incorrect online status, have %v, want %v", want, have)
+	}
+	cpu1Online, err := cpus[1].Online()
+	if err != nil && !errors.Is(err, os.ErrNotExist) {
+		t.Fatal(err)
+	}
+	if want, have := false, cpu1Online; want != have {
+		t.Errorf("incorrect online status, have %v, want %v", want, have)
+	}
+}
+
 func TestCPUThermalThrottle(t *testing.T) {
 	fs, err := NewFS(sysTestFixtures)
 	if err != nil {
@@ -104,23 +133,38 @@ func TestSystemCpufreq(t *testing.T) {
 	}
 
 	systemCpufreq := []SystemCPUCpufreqStats{
-		// Has missing `cpuinfo_cur_freq` file.
+		// The following files are missing for the first CPU:
+		// * `cpuinfo_cur_freq`
+		// * `time_in_state`
+		// * `total_trans`
 		{
-			Name:                     "0",
-			CpuinfoCurrentFrequency:  nil,
-			CpuinfoMinimumFrequency:  makeUint64(800000),
-			CpuinfoMaximumFrequency:  makeUint64(2400000),
-			CpuinfoTransitionLatency: makeUint64(0),
-			ScalingCurrentFrequency:  makeUint64(1219917),
-			ScalingMinimumFrequency:  makeUint64(800000),
-			ScalingMaximumFrequency:  makeUint64(2400000),
-			AvailableGovernors:       "performance powersave",
-			Driver:                   "intel_pstate",
-			Governor:                 "powersave",
-			RelatedCpus:              "0",
-			SetSpeed:                 "<unsupported>",
+			Name:                             "0",
+			CpuinfoCurrentFrequency:          nil,
+			CpuinfoMinimumFrequency:          makeUint64(800000),
+			CpuinfoMaximumFrequency:          makeUint64(2400000),
+			CpuinfoTransitionLatency:         makeUint64(0),
+			ScalingCurrentFrequency:          makeUint64(1219917),
+			ScalingMinimumFrequency:          makeUint64(800000),
+			ScalingMaximumFrequency:          makeUint64(2400000),
+			AvailableGovernors:               "performance powersave",
+			Driver:                           "intel_pstate",
+			Governor:                         "powersave",
+			RelatedCpus:                      "0",
+			SetSpeed:                         "<unsupported>",
+			CpuinfoFrequencyDuration:         nil,
+			CpuinfoFrequencyTransitionsTotal: nil,
+			CpuinfoTransitionTable: &[][]uint64{
+				{0, 3600000, 3400000, 3200000, 3000000, 2800000},
+				{3600000, 0, 5, 0, 0, 0},
+				{3400000, 4, 0, 2, 0, 0},
+				{3200000, 0, 1, 0, 2, 0},
+				{3000000, 0, 0, 1, 0, 3},
+				{2800000, 0, 0, 0, 2, 0},
+			},
 		},
-		// Has missing `scaling_cur_freq` file.
+		// The following files are missing for the second CPU:
+		// * `scaling_cur_freq`
+		// * `trans_table`
 		{
 			Name:                     "1",
 			CpuinfoCurrentFrequency:  makeUint64(1200195),
@@ -135,6 +179,15 @@ func TestSystemCpufreq(t *testing.T) {
 			Governor:                 "powersave",
 			RelatedCpus:              "1",
 			SetSpeed:                 "<unsupported>",
+			CpuinfoFrequencyDuration: &map[uint64]uint64{
+				3600000: 2089,
+				3400000: 136,
+				3200000: 34,
+				3000000: 67,
+				2800000: 172488,
+			},
+			CpuinfoFrequencyTransitionsTotal: makeUint64(20),
+			CpuinfoTransitionTable:           nil,
 		},
 	}
 
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index c5d9a6d..4bc508b 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
@@ -13441,6 +13441,11 @@ Mode: 775
 Path: fixtures/sys/devices/system/cpu/cpu0/cpufreq
 SymlinkTo: ../cpufreq/policy0
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpu0/online
+Lines: 1
+1EOF
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu/cpu0/thermal_throttle
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -13548,6 +13553,23 @@ Lines: 1
 <unsupported>
 Mode: 664
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state
+Lines: 5
+3600000 2089
+3400000 136
+3200000 34
+3000000 67
+2800000 172488
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/total_trans
+Lines: 1
+20
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu/cpu1/thermal_throttle
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -13663,6 +13685,20 @@ Lines: 1
 <unsupported>
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats/trans_table
+Lines: 7
+From  :    To
+        :   3600000   3400000   3200000   3000000   2800000
+3600000:         0         5         0         0         0
+3400000:         4         0         2         0         0
+3200000:         0         1         0         2         0
+3000000:         0         0         1         0         3
+2800000:         0         0         0         2         0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu/cpufreq/policy1
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -14757,6 +14793,40 @@ Lines: 1
 4096
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/fs/selinux
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/fs/selinux/avc
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/fs/selinux/avc/cache_stats
+Lines: 17
+lookups hits misses allocations reclaims frees
+6603240 6601784 1456 1456 1424 1479
+5110559 5109586 973 973 1152 1162
+6720426 6719001 1425 1425 1488 1515
+4713015 4711743 1272 1272 1200 1211
+6949114 6947453 1661 1661 1504 1555
+5624578 5622931 1647 1647 1328 1347
+5884601 5882982 1619 1619 1488 1525
+3170931 3170061 870 870 832 858
+7336127 7334467 1660 1660 1632 1644
+2853424 2852652 772 772 704 712
+7596237 7594408 1829 1829 1936 1960
+4491113 4490186 927 927 832 854
+7319238 7317738 1500 1500 1504 1538
+4945119 4944151 968 968 912 943
+7610170 7608283 1887 1887 1648 1695
+4662892 4662026 866 866 816 828
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/fs/selinux/avc/hash_stats
+Lines: 3
+entries: 503
+buckets used: 257/512
+longest chain: 8
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/fs/xfs
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -