Repository: prometheus/procfs
Pull request #556 (2023-08-05 17:18:06)

infiniband: do not fail if board_id is not present
Whilst a large majority do, not _all_ InfiniBand drivers expose a board_id in sysfs.

Fixes: https://github.com/prometheus/node_exporter/issues/2769

SuperQ: I wonder if we should make the values in the struct `*string` and set it to nil if the file is not found.
dswarbrick: > I wonder if we should make the values in the struct `*string` and set it to nil if the file is not found.

Doing so means that consumers of this need to check for non-nil before using the struct members. I'm not sure if that's a good thing or not. Given that Prometheus does not store empty-string labels, my gut feeling is that it's more convenient to simply leave these struct members as empty strings if not found, meaning that node_exporter can just blindly assign them to labels.

Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 433f71b..8ace97b 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -18,15 +18,15 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
       - name: install Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
         with:
           go-version: 1.20.x
       - name: Install snmp_exporter/generator dependencies
         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@v3.4.0
+        uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
         with:
-          version: v1.53.3
+          version: v1.54.2
diff --git a/Makefile.common b/Makefile.common
index 0ce7ea4..062a281 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -61,7 +61,7 @@ PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_
 SKIP_GOLANGCI_LINT :=
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.53.3
+GOLANGCI_LINT_VERSION ?= v1.54.2
 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
 # windows isn't included here because of the path separator being different.
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
diff --git a/fs_statfs_notype.go b/fs_statfs_notype.go
index 13d74e3..134767d 100644
--- a/fs_statfs_notype.go
+++ b/fs_statfs_notype.go
@@ -11,8 +11,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//go:build netbsd || openbsd || solaris || windows || nostatfs
-// +build netbsd openbsd solaris windows nostatfs
+//go:build !freebsd && !linux
+// +build !freebsd,!linux
 
 package procfs
 
diff --git a/fs_statfs_type.go b/fs_statfs_type.go
index bee1514..80df79c 100644
--- a/fs_statfs_type.go
+++ b/fs_statfs_type.go
@@ -11,8 +11,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//go:build !netbsd && !openbsd && !solaris && !windows && !nostatfs
-// +build !netbsd,!openbsd,!solaris,!windows,!nostatfs
+//go:build freebsd || linux
+// +build freebsd linux
 
 package procfs
 
diff --git a/go.mod b/go.mod
index d582993..95cd65f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,5 @@ go 1.19
 require (
 	github.com/google/go-cmp v0.5.9
 	golang.org/x/sync v0.3.0
-	golang.org/x/sys v0.9.0
+	golang.org/x/sys v0.11.0
 )
diff --git a/go.sum b/go.sum
index 8f019b2..4d2f8b9 100644
--- a/go.sum
+++ b/go.sum
@@ -2,5 +2,5 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/mountstats.go b/mountstats.go
index 852c8c4..9d8af6d 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -44,6 +44,14 @@ const (
 
 	fieldTransport11TCPLen = 13
 	fieldTransport11UDPLen = 10
+
+	// kernel version >= 4.14 MaxLen
+	// See: https://elixir.bootlin.com/linux/v6.4.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L393
+	fieldTransport11RDMAMaxLen = 28
+
+	// kernel version <= 4.2 MinLen
+	// See: https://elixir.bootlin.com/linux/v4.2.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L331
+	fieldTransport11RDMAMinLen = 20
 )
 
 // A Mount is a device mount parsed from /proc/[pid]/mountstats.
@@ -233,6 +241,33 @@ type NFSTransportStats struct {
 	// A running counter, incremented on each request as the current size of the
 	// pending queue.
 	CumulativePendingQueue uint64
+
+	// Stats below only available with stat version 1.1.
+	// Transport over RDMA
+
+	// accessed when sending a call
+	ReadChunkCount   uint64
+	WriteChunkCount  uint64
+	ReplyChunkCount  uint64
+	TotalRdmaRequest uint64
+
+	// rarely accessed error counters
+	PullupCopyCount      uint64
+	HardwayRegisterCount uint64
+	FailedMarshalCount   uint64
+	BadReplyCount        uint64
+	MrsRecovered         uint64
+	MrsOrphaned          uint64
+	MrsAllocated         uint64
+	EmptySendctxQ        uint64
+
+	// accessed when receiving a reply
+	TotalRdmaReply    uint64
+	FixupCopyCount    uint64
+	ReplyWaitsForSend uint64
+	LocalInvNeeded    uint64
+	NomsgCallCount    uint64
+	BcallCount        uint64
 }
 
 // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
@@ -587,14 +622,17 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
 			expectedLength = fieldTransport11TCPLen
 		} else if protocol == "udp" {
 			expectedLength = fieldTransport11UDPLen
+		} else if protocol == "rdma" {
+			expectedLength = fieldTransport11RDMAMinLen
 		} else {
 			return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
 		}
-		if len(ss) != expectedLength {
-			return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v", ErrFileParse, ss)
+		if (len(ss) != expectedLength && (protocol == "tcp" || protocol == "udp")) ||
+			(protocol == "rdma" && len(ss) < expectedLength) {
+			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", ErrFileParse, statVersion)
+		return nil, fmt.Errorf("%s: 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
@@ -604,7 +642,9 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
 	// Note: slice length must be set to length of v1.1 stats to avoid a panic when
 	// only v1.0 stats are present.
 	// See: https://github.com/prometheus/node_exporter/issues/571.
-	ns := make([]uint64, fieldTransport11TCPLen)
+	//
+	// Note: NFS Over RDMA slice length is fieldTransport11RDMAMaxLen
+	ns := make([]uint64, fieldTransport11RDMAMaxLen+3)
 	for i, s := range ss {
 		n, err := strconv.ParseUint(s, 10, 64)
 		if err != nil {
@@ -622,9 +662,14 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
 	// we set them to 0 here.
 	if protocol == "udp" {
 		ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
+	} else if protocol == "tcp" {
+		ns = append(ns[:fieldTransport11TCPLen], make([]uint64, fieldTransport11RDMAMaxLen-fieldTransport11TCPLen+3)...)
+	} else if protocol == "rdma" {
+		ns = append(ns[:fieldTransport10TCPLen], append(make([]uint64, 3), ns[fieldTransport10TCPLen:]...)...)
 	}
 
 	return &NFSTransportStats{
+		// NFS xprt over tcp or udp
 		Protocol:                 protocol,
 		Port:                     ns[0],
 		Bind:                     ns[1],
@@ -636,8 +681,32 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
 		BadTransactionIDs:        ns[7],
 		CumulativeActiveRequests: ns[8],
 		CumulativeBacklog:        ns[9],
-		MaximumRPCSlotsUsed:      ns[10],
-		CumulativeSendingQueue:   ns[11],
-		CumulativePendingQueue:   ns[12],
+
+		// NFS xprt over tcp or udp
+		// And statVersion 1.1
+		MaximumRPCSlotsUsed:    ns[10],
+		CumulativeSendingQueue: ns[11],
+		CumulativePendingQueue: ns[12],
+
+		// NFS xprt over rdma
+		// And stat Version 1.1
+		ReadChunkCount:       ns[13],
+		WriteChunkCount:      ns[14],
+		ReplyChunkCount:      ns[15],
+		TotalRdmaRequest:     ns[16],
+		PullupCopyCount:      ns[17],
+		HardwayRegisterCount: ns[18],
+		FailedMarshalCount:   ns[19],
+		BadReplyCount:        ns[20],
+		MrsRecovered:         ns[21],
+		MrsOrphaned:          ns[22],
+		MrsAllocated:         ns[23],
+		EmptySendctxQ:        ns[24],
+		TotalRdmaReply:       ns[25],
+		FixupCopyCount:       ns[26],
+		ReplyWaitsForSend:    ns[27],
+		LocalInvNeeded:       ns[28],
+		NomsgCallCount:       ns[29],
+		BcallCount:           ns[30],
 	}, nil
 }
diff --git a/nfs/nfs.go b/nfs/nfs.go
index 7292102..f17fa51 100644
--- a/nfs/nfs.go
+++ b/nfs/nfs.go
@@ -201,51 +201,56 @@ type ServerV4Stats struct {
 // V4Ops models the "proc4ops" line: NFSv4 operations
 // Variable list.
 // See:
-// - v4.0 https://tools.ietf.org/html/rfc3010 (38 operations)
+// - v4.0 https://tools.ietf.org/html/rfc3010 (38/39 operations)
+//   - nfs == v2.5.x 38 field : https://elixir.bootlin.com/linux/v2.5.75/source/include/linux/nfs4.h#L52
+//   - nfs >= v2.6.x 39 field : https://elixir.bootlin.com/linux/v2.6.39.4/source/include/linux/nfs4.h#L233
+//
 // - v4.1 https://tools.ietf.org/html/rfc5661 (58 operations)
 // - v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations)
 //
 //nolint:godot
 type V4Ops struct {
 	//Values       uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct?
-	Op0Unused    uint64
-	Op1Unused    uint64
-	Op2Future    uint64
-	Access       uint64
-	Close        uint64
-	Commit       uint64
-	Create       uint64
-	DelegPurge   uint64
-	DelegReturn  uint64
-	GetAttr      uint64
-	GetFH        uint64
-	Link         uint64
-	Lock         uint64
-	Lockt        uint64
-	Locku        uint64
-	Lookup       uint64
-	LookupRoot   uint64
-	Nverify      uint64
-	Open         uint64
-	OpenAttr     uint64
-	OpenConfirm  uint64
-	OpenDgrd     uint64
-	PutFH        uint64
-	PutPubFH     uint64
-	PutRootFH    uint64
-	Read         uint64
-	ReadDir      uint64
-	ReadLink     uint64
-	Remove       uint64
-	Rename       uint64
-	Renew        uint64
-	RestoreFH    uint64
-	SaveFH       uint64
-	SecInfo      uint64
-	SetAttr      uint64
-	Verify       uint64
-	Write        uint64
-	RelLockOwner uint64
+	Op0Unused          uint64
+	Op1Unused          uint64
+	Op2Future          uint64
+	Access             uint64
+	Close              uint64
+	Commit             uint64
+	Create             uint64
+	DelegPurge         uint64
+	DelegReturn        uint64
+	GetAttr            uint64
+	GetFH              uint64
+	Link               uint64
+	Lock               uint64
+	Lockt              uint64
+	Locku              uint64
+	Lookup             uint64
+	LookupRoot         uint64
+	Nverify            uint64
+	Open               uint64
+	OpenAttr           uint64
+	OpenConfirm        uint64
+	OpenDgrd           uint64
+	PutFH              uint64
+	PutPubFH           uint64
+	PutRootFH          uint64
+	Read               uint64
+	ReadDir            uint64
+	ReadLink           uint64
+	Remove             uint64
+	Rename             uint64
+	Renew              uint64
+	RestoreFH          uint64
+	SaveFH             uint64
+	SecInfo            uint64
+	SetAttr            uint64
+	SetClientID        uint64
+	SetClientIDConfirm uint64
+	Verify             uint64
+	Write              uint64
+	RelLockOwner       uint64
 }
 
 // ClientRPCStats models all stats from /proc/net/rpc/nfs.
@@ -270,6 +275,7 @@ type ServerRPCStats struct {
 	V3Stats        V3Stats
 	ServerV4Stats  ServerV4Stats
 	V4Ops          V4Ops
+	WdelegGetattr  uint64
 }
 
 // FS represents the pseudo-filesystem proc, which provides an interface to
diff --git a/nfs/parse.go b/nfs/parse.go
index 5e78c54..76e74cb 100644
--- a/nfs/parse.go
+++ b/nfs/parse.go
@@ -272,45 +272,53 @@ func parseV4Ops(v []uint64) (V4Ops, error) {
 		return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v)
 	}
 
+	// nfs v2.5.x 39field and >=v2.6.x 40 field;
+	v40 := uint64(0)
+	if values > 39 {
+		v40 = v[40]
+	}
+
 	stats := V4Ops{
-		Op0Unused:    v[1],
-		Op1Unused:    v[2],
-		Op2Future:    v[3],
-		Access:       v[4],
-		Close:        v[5],
-		Commit:       v[6],
-		Create:       v[7],
-		DelegPurge:   v[8],
-		DelegReturn:  v[9],
-		GetAttr:      v[10],
-		GetFH:        v[11],
-		Link:         v[12],
-		Lock:         v[13],
-		Lockt:        v[14],
-		Locku:        v[15],
-		Lookup:       v[16],
-		LookupRoot:   v[17],
-		Nverify:      v[18],
-		Open:         v[19],
-		OpenAttr:     v[20],
-		OpenConfirm:  v[21],
-		OpenDgrd:     v[22],
-		PutFH:        v[23],
-		PutPubFH:     v[24],
-		PutRootFH:    v[25],
-		Read:         v[26],
-		ReadDir:      v[27],
-		ReadLink:     v[28],
-		Remove:       v[29],
-		Rename:       v[30],
-		Renew:        v[31],
-		RestoreFH:    v[32],
-		SaveFH:       v[33],
-		SecInfo:      v[34],
-		SetAttr:      v[35],
-		Verify:       v[36],
-		Write:        v[37],
-		RelLockOwner: v[38],
+		Op0Unused:          v[1],
+		Op1Unused:          v[2],
+		Op2Future:          v[3],
+		Access:             v[4],
+		Close:              v[5],
+		Commit:             v[6],
+		Create:             v[7],
+		DelegPurge:         v[8],
+		DelegReturn:        v[9],
+		GetAttr:            v[10],
+		GetFH:              v[11],
+		Link:               v[12],
+		Lock:               v[13],
+		Lockt:              v[14],
+		Locku:              v[15],
+		Lookup:             v[16],
+		LookupRoot:         v[17],
+		Nverify:            v[18],
+		Open:               v[19],
+		OpenAttr:           v[20],
+		OpenConfirm:        v[21],
+		OpenDgrd:           v[22],
+		PutFH:              v[23],
+		PutPubFH:           v[24],
+		PutRootFH:          v[25],
+		Read:               v[26],
+		ReadDir:            v[27],
+		ReadLink:           v[28],
+		Remove:             v[29],
+		Rename:             v[30],
+		Renew:              v[31],
+		RestoreFH:          v[32],
+		SaveFH:             v[33],
+		SecInfo:            v[34],
+		SetAttr:            v[35],
+		SetClientID:        v[36],
+		SetClientIDConfirm: v[37],
+		Verify:             v[38],
+		Write:              v[39],
+		RelLockOwner:       v40,
 	}
 
 	return stats, nil
diff --git a/nfs/parse_nfsd.go b/nfs/parse_nfsd.go
index 857ae3b..66828e5 100644
--- a/nfs/parse_nfsd.go
+++ b/nfs/parse_nfsd.go
@@ -73,6 +73,8 @@ func ParseServerRPCStats(r io.Reader) (*ServerRPCStats, error) {
 			stats.ServerV4Stats, err = parseServerV4Stats(values)
 		case "proc4ops":
 			stats.V4Ops, err = parseV4Ops(values)
+		case "wdeleg_getattr":
+			stats.WdelegGetattr = values[0]
 		default:
 			return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
 		}
diff --git a/proc_fdinfo.go b/proc_fdinfo.go
index 4b7933e..fa761b3 100644
--- a/proc_fdinfo.go
+++ b/proc_fdinfo.go
@@ -26,6 +26,7 @@ var (
 	rPos          = regexp.MustCompile(`^pos:\s+(\d+)$`)
 	rFlags        = regexp.MustCompile(`^flags:\s+(\d+)$`)
 	rMntID        = regexp.MustCompile(`^mnt_id:\s+(\d+)$`)
+	rIno          = regexp.MustCompile(`^ino:\s+(\d+)$`)
 	rInotify      = regexp.MustCompile(`^inotify`)
 	rInotifyParts = regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)(?:\s+mask:([0-9a-f]+))?`)
 )
@@ -40,6 +41,8 @@ type ProcFDInfo struct {
 	Flags string
 	// Mount point ID
 	MntID string
+	// Inode number
+	Ino string
 	// List of inotify lines (structured) in the fdinfo file (kernel 3.8+ only)
 	InotifyInfos []InotifyInfo
 }
@@ -51,7 +54,7 @@ func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
 		return nil, err
 	}
 
-	var text, pos, flags, mntid string
+	var text, pos, flags, mntid, ino string
 	var inotify []InotifyInfo
 
 	scanner := bufio.NewScanner(bytes.NewReader(data))
@@ -63,6 +66,8 @@ func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
 			flags = rFlags.FindStringSubmatch(text)[1]
 		} else if rMntID.MatchString(text) {
 			mntid = rMntID.FindStringSubmatch(text)[1]
+		} else if rIno.MatchString(text) {
+			ino = rIno.FindStringSubmatch(text)[1]
 		} else if rInotify.MatchString(text) {
 			newInotify, err := parseInotifyInfo(text)
 			if err != nil {
@@ -77,6 +82,7 @@ func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
 		Pos:          pos,
 		Flags:        flags,
 		MntID:        mntid,
+		Ino:          ino,
 		InotifyInfos: inotify,
 	}
 
diff --git a/proc_maps.go b/proc_maps.go
index 727549a..7e75c28 100644
--- a/proc_maps.go
+++ b/proc_maps.go
@@ -63,17 +63,17 @@ type ProcMap struct {
 // parseDevice parses the device token of a line and converts it to a dev_t
 // (mkdev) like structure.
 func parseDevice(s string) (uint64, error) {
-	toks := strings.Split(s, ":")
-	if len(toks) < 2 {
-		return 0, fmt.Errorf("%w: unexpected number of fields, expected: 2, got: %q", ErrFileParse, len(toks))
+	i := strings.Index(s, ":")
+	if i == -1 {
+		return 0, fmt.Errorf("%w: expected separator `:` in %s", ErrFileParse, s)
 	}
 
-	major, err := strconv.ParseUint(toks[0], 16, 0)
+	major, err := strconv.ParseUint(s[0:i], 16, 0)
 	if err != nil {
 		return 0, err
 	}
 
-	minor, err := strconv.ParseUint(toks[1], 16, 0)
+	minor, err := strconv.ParseUint(s[i+1:], 16, 0)
 	if err != nil {
 		return 0, err
 	}
@@ -93,17 +93,17 @@ func parseAddress(s string) (uintptr, error) {
 
 // parseAddresses parses the start-end address.
 func parseAddresses(s string) (uintptr, uintptr, error) {
-	toks := strings.Split(s, "-")
-	if len(toks) < 2 {
-		return 0, 0, fmt.Errorf("%w: invalid address", ErrFileParse)
+	idx := strings.Index(s, "-")
+	if idx == -1 {
+		return 0, 0, fmt.Errorf("%w: expected separator `-` in %s", ErrFileParse, s)
 	}
 
-	saddr, err := parseAddress(toks[0])
+	saddr, err := parseAddress(s[0:idx])
 	if err != nil {
 		return 0, 0, err
 	}
 
-	eaddr, err := parseAddress(toks[1])
+	eaddr, err := parseAddress(s[idx+1:])
 	if err != nil {
 		return 0, 0, err
 	}
diff --git a/proc_status.go b/proc_status.go
index c055d07..46307f5 100644
--- a/proc_status.go
+++ b/proc_status.go
@@ -23,7 +23,7 @@ import (
 )
 
 // ProcStatus provides status information about the process,
-// read from /proc/[pid]/stat.
+// read from /proc/[pid]/status.
 type ProcStatus struct {
 	// The process ID.
 	PID int
@@ -32,6 +32,8 @@ type ProcStatus struct {
 
 	// Thread group ID.
 	TGID int
+	// List of Pid namespace.
+	NSpids []uint64
 
 	// Peak virtual memory size.
 	VmPeak uint64 // nolint:revive
@@ -127,6 +129,8 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt
 		copy(s.UIDs[:], strings.Split(vString, "\t"))
 	case "Gid":
 		copy(s.GIDs[:], strings.Split(vString, "\t"))
+	case "NSpid":
+		s.NSpids = calcNSPidsList(vString)
 	case "VmPeak":
 		s.VmPeak = vUintBytes
 	case "VmSize":
@@ -200,3 +204,18 @@ func calcCpusAllowedList(cpuString string) []uint64 {
 	sort.Slice(g, func(i, j int) bool { return g[i] < g[j] })
 	return g
 }
+
+func calcNSPidsList(nspidsString string) []uint64 {
+	s := strings.Split(nspidsString, " ")
+	var nspids []uint64
+
+	for _, nspid := range s {
+		nspid, _ := strconv.ParseUint(nspid, 10, 64)
+		if nspid == 0 {
+			continue
+		}
+		nspids = append(nspids, nspid)
+	}
+
+	return nspids
+}
diff --git a/sysfs/class_infiniband.go b/sysfs/class_infiniband.go
index 652b544..4c10fe7 100644
--- a/sysfs/class_infiniband.go
+++ b/sysfs/class_infiniband.go
@@ -121,16 +121,24 @@ func (fs FS) InfiniBandClass() (InfiniBandClass, error) {
 }
 
 // Parse one InfiniBand device.
+// Refer to https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-infiniband
 func (fs FS) parseInfiniBandDevice(name string) (*InfiniBandDevice, error) {
 	path := fs.sys.Path(infinibandClassPath, name)
 	device := InfiniBandDevice{Name: name}
 
-	for _, f := range [...]string{"board_id", "fw_ver", "hca_type"} {
+	// fw_ver is exposed by all InfiniBand drivers since kernel version 4.10.
+	value, err := util.SysReadFile(filepath.Join(path, "fw_ver"))
+	if err != nil {
+		return nil, fmt.Errorf("failed to read HCA firmware version: %w", err)
+	}
+	device.FirmwareVersion = value
+
+	// Not all InfiniBand drivers expose all of these.
+	for _, f := range [...]string{"board_id", "hca_type"} {
 		name := filepath.Join(path, f)
 		value, err := util.SysReadFile(name)
 		if err != nil {
-			// Not all InfiniBand drivers provide hca_type.
-			if os.IsNotExist(err) && (f == "hca_type") {
+			if os.IsNotExist(err) {
 				continue
 			}
 			return nil, fmt.Errorf("failed to read file %q: %w", name, err)
@@ -139,8 +147,6 @@ func (fs FS) parseInfiniBandDevice(name string) (*InfiniBandDevice, error) {
 		switch f {
 		case "board_id":
 			device.BoardID = value
-		case "fw_ver":
-			device.FirmwareVersion = value
 		case "hca_type":
 			device.HCAType = value
 		}
diff --git a/sysfs/vulnerability.go b/sysfs/vulnerability.go
index 08927a2..5a14a3e 100644
--- a/sysfs/vulnerability.go
+++ b/sysfs/vulnerability.go
@@ -17,7 +17,6 @@
 package sysfs
 
 import (
-	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
@@ -27,12 +26,14 @@ const (
 	notAffected = "not affected" // based on: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
 	vulnerable  = "vulnerable"
 	mitigation  = "mitigation"
+	unknown     = "unknown"
 )
 
 const (
 	VulnerabilityStateNotAffected = iota
 	VulnerabilityStateVulnerable
 	VulnerabilityStateMitigation
+	VulnerabilityStateUnknown
 )
 
 var (
@@ -42,6 +43,7 @@ var (
 		VulnerabilityStateNotAffected: notAffected,
 		VulnerabilityStateVulnerable:  vulnerable,
 		VulnerabilityStateMitigation:  mitigation,
+		VulnerabilityStateUnknown:     unknown,
 	}
 )
 
@@ -98,9 +100,17 @@ func parseVulnerability(name, rawContent string) (*Vulnerability, error) {
 		if len(m) > 1 {
 			v.Mitigation = strings.Join(m[1:], " ")
 		}
+	case strings.HasPrefix(rawContentLower, unknown):
+		v.State = VulnerabilityStateUnknown
+		m := strings.Fields(rawContent)
+		if len(m) > 1 {
+			v.Mitigation = strings.Join(m[1:], " ")
+		}
 	default:
-		return nil, fmt.Errorf("unknown vulnerability state for %s: %s", name, rawContent)
-
+		// Output the raw data obtained from the vulnerability, with state
+		// unknown, rather than erroring out
+		v.State = VulnerabilityStateUnknown
+		v.Mitigation = rawContent
 	}
 	return v, nil
 }


Test patch:
diff --git a/mountstats_test.go b/mountstats_test.go
index 3d23671..43903e9 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -372,6 +372,102 @@ func TestMountStats(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "NFS xprt over rdma proto",
+			s: `device <nfsserver>:<nfsmount> mounted on <mountpoint> with fstype nfs statvers=1.1
+        opts:   ro,vers=3,rsize=1048576,wsize=1048576,namlen=255,acregmin=120,acregmax=120,acdirmin=120,acdirmax=120,hard,nocto,forcerdirplus,proto=rdma,nconnect=16,port=20049,timeo=600,retrans=2,sec=sys,mountaddr=172.16.40.20,mountvers=3,mountport=0,mountproto=tcp,local_lock=none
+        age:    1270876
+        caps:   caps=0xf,wtmult=4096,dtsize=131072,bsize=0,namlen=255
+        sec:    flavor=1,pseudoflavor=1
+        events: 512052 36601115 0 68 1498583 16514 38815015 0 41584 2654459933 0 0 0 0 1527715 0 0 1498575 0 0 0 0 0 0 0 0 0
+        bytes:  3104202770327296 0 0 0 2013200952170479 0 491504202537 0
+        RPC iostats version: 1.1  p/v: 100003/3 (nfs)
+        xprt:   rdma 0 0 5808 62 0 494490723 494490687 36 10032963746 1282789 107150285 1226637531 2673889 135120843409861 135119397156505 266368832 75716996 0 7853 0 0 0 0 119328 1336431717 0 96
+        per-op statistics
+                NULL: 16 16 0 640 384 320 11 331 0
+`,
+			mounts: []*Mount{{
+				Device: "<nfsserver>:<nfsmount>",
+				Mount:  "<mountpoint>",
+				Type:   "nfs",
+				Stats: &MountStatsNFS{
+					StatVersion: "1.1",
+					Opts: map[string]string{"acdirmax": "120", "acdirmin": "120", "acregmax": "120",
+						"acregmin": "120", "forcerdirplus": "", "hard": "", "local_lock": "none",
+						"mountaddr": "172.16.40.20", "mountport": "0", "mountproto": "tcp", "mountvers": "3",
+						"namlen": "255", "nconnect": "16", "nocto": "", "port": "20049", "proto": "rdma",
+						"retrans": "2", "ro": "", "rsize": "1048576", "sec": "sys", "timeo": "600",
+						"vers": "3", "wsize": "1048576"},
+					Age: 1270876 * time.Second,
+					Bytes: NFSBytesStats{
+						Read:      3104202770327296,
+						ReadTotal: 2013200952170479,
+						ReadPages: 491504202537,
+					},
+					Events: NFSEventsStats{
+						InodeRevalidate:     512052,
+						DnodeRevalidate:     36601115,
+						AttributeInvalidate: 68,
+						VFSOpen:             1498583,
+						VFSLookup:           16514,
+						VFSAccess:           38815015,
+						VFSReadPage:         41584,
+						VFSReadPages:        2654459933,
+						VFSFlush:            1527715,
+						VFSFileRelease:      1498575,
+					},
+					Operations: []NFSOperationStats{
+						{
+							Operation:                           "NULL",
+							Requests:                            16,
+							Transmissions:                       16,
+							MajorTimeouts:                       0,
+							BytesSent:                           640,
+							BytesReceived:                       384,
+							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,
+					},
+				},
+			}},
+		},
 	}
 
 	for i, tt := range tests {
@@ -518,4 +614,61 @@ device fs.example.com:/volume4/apps/home-automation/node-red-data mounted on /va
 			LOOKUPP: 0 0 0 0 0 0 0 0 0
 		LAYOUTERROR: 0 0 0 0 0 0 0 0 0
 `
+
+	extendedRDMAExampleALLMountstats = `device <nfsserver>:<nfsmount> mounted on <mountpoint> with fstype nfs statvers=1.1
+        opts:   ro,vers=3,rsize=1048576,wsize=1048576,namlen=255,acregmin=120,acregmax=120,acdirmin=120,acdirmax=120,hard,nocto,forcerdirplus,proto=rdma,nconnect=16,port=20049,timeo=600,retrans=2,sec=sys,mountaddr=172.16.40.20,mountvers=3,mountport=0,mountproto=tcp,local_lock=none
+        age:    1270876
+        caps:   caps=0xf,wtmult=4096,dtsize=131072,bsize=0,namlen=255
+        sec:    flavor=1,pseudoflavor=1
+        events: 512052 36601115 0 68 1498583 16514 38815015 0 41584 2654459933 0 0 0 0 1527715 0 0 1498575 0 0 0 0 0 0 0 0 0
+        bytes:  3104202770327296 0 0 0 2013200952170479 0 491504202537 0
+        RPC iostats version: 1.1  p/v: 100003/3 (nfs)
+        xprt:   rdma 0 0 5808 62 0 494490723 494490687 36 10032963746 1282789 107150285 1226637531 2673889 135120843409861 135119397156505 266368832 75716996 0 7853 0 0 0 0 119328 1336431717 0 96
+        xprt:   rdma 0 0 14094 145 0 492392334 492392307 27 7078693624 2509627 105561370 1280878332 2659446 142218924010291 142217463504063 276368040 94761838 0 7610 0 0 0 0 207977 1389069860 0 103
+        xprt:   rdma 0 0 16107 156 0 522755125 522755092 33 9119562599 1147699 109077860 1491898147 2566003 167152062826463 167149287506014 284931680 83011025 0 6229 0 0 0 0 221408 1603518232 0 82
+        xprt:   rdma 0 0 7808 82 0 441542046 441542010 36 7226132207 2519174 111096004 955223347 2676765 105741904708009 105740125663595 275613584 80373159 0 8893 0 0 0 0 149479 1068962768 0 76
+        xprt:   rdma 0 0 15018 167 0 508091827 508091764 63 19817677255 36702583 108265928 1258185459 2438516 138247436686102 138246196289594 270162080 74962306 0 13328 0 0 0 0 268433 1368837472 0 66
+        xprt:   rdma 0 0 14321 149 0 530246310 530246275 35 9723190432 2392024 111099700 1494204555 2589805 166691166581904 166689567426908 289995492 85067377 0 8010 0 0 0 0 214511 1607864447 0 100
+        xprt:   rdma 0 0 7863 84 0 459019689 459019642 47 11809253102 1716688 111825219 1032758664 2564226 114416685286438 114414936423706 290494252 73702102 0 6927 0 0 0 0 134453 1147121864 0 79
+        xprt:   rdma 0 0 7702 84 3 497598986 497598931 55 11816221496 3924722 106922130 1382063307 2506108 153967067193941 153965665472218 286222584 84094006 0 5875 0 0 0 0 127347 1491469045 0 66
+        xprt:   rdma 0 0 18341 202 0 477721151 477721073 78 15204400959 40562626 106645745 1291616653 3091375 144533696686651 144529688231163 278135800 73821525 0 6795 0 0 0 0 251097 1401327563 0 64
+        xprt:   rdma 0 0 8228 90 4 453155092 453155063 29 7884786894 1591225 112197590 1026006338 2742688 114591819605673 114590175821191 275541944 85857259 0 7487 0 0 0 0 143044 1140917892 0 76
+        xprt:   rdma 0 0 7843 83 0 446480377 446480324 53 12267986428 2958997 111971246 963162784 2693433 107176282309753 107174637802555 290269096 101100410 0 7825 0 0 0 0 141735 1077797328 0 83
+        xprt:   rdma 0 0 7582 86 0 423315608 423315567 41 10197484604 2076993 109207538 785978455 2650354 86090211449474 86088475571312 279912524 87676008 0 7491 0 0 0 0 137533 897807641 0 101
+        xprt:   rdma 0 0 7767 84 0 482538465 482538424 41 8935200479 1344778 112200583 1192341640 2644896 132860698423762 132858881459050 273354060 75337030 0 5941 0 0 0 0 127842 1307164736 0 97
+        xprt:   rdma 0 0 14526 148 2 537745063 537745007 56 20756072620 3970332320 109539564 1363647371 2503250 148793734936250 148791264145401 291888720 90344151 0 7471 0 0 0 0 211057 1475661285 0 82
+        xprt:   rdma 0 0 14300 151 0 495357347 495357316 31 8703101643 1451809 112315311 1303804607 2620502 145680743007170 145678880292235 288046696 98018259 0 7241 0 0 0 0 209396 1418712657 0 139
+        xprt:   rdma 0 0 7700 82 0 466611083 466611050 33 8540498291 4082864 114740300 1059770596 2523155 117376668239921 117375375683167 260927576 78437075 0 6691 0 0 0 0 130878 1177008175 1 76
+        per-op statistics
+                NULL: 16 16 0 640 384 320 11 331 0
+             GETATTR: 512052 512052 0 79823516 57349824 107131 612667 751847 0
+             SETATTR: 0 0 0 0 0 0 0 0 0
+              LOOKUP: 16713 16713 0 3040536 3706344 560 17488 20232 346
+              ACCESS: 211705 211705 0 33860920 25404600 37059 229754 283822 0
+            READLINK: 0 0 0 0 0 0 0 0 0
+                READ: 2654501510 2654501510 0 445911966900 2013540728551504 6347457114 31407021389 37927280438 0
+               WRITE: 0 0 0 0 0 0 0 0 0
+              CREATE: 0 0 0 0 0 0 0 0 0
+               MKDIR: 0 0 0 0 0 0 0 0 0
+             SYMLINK: 0 0 0 0 0 0 0 0 0
+               MKNOD: 0 0 0 0 0 0 0 0 0
+              REMOVE: 0 0 0 0 0 0 0 0 0
+               RMDIR: 0 0 0 0 0 0 0 0 0
+              RENAME: 0 0 0 0 0 0 0 0 0
+                LINK: 0 0 0 0 0 0 0 0 0
+             READDIR: 0 0 0 0 0 0 0 0 0
+         READDIRPLUS: 0 0 0 0 0 0 0 0 0
+              FSSTAT: 56356 56356 0 6243572 9467808 82068 74356 159001 0
+              FSINFO: 2 2 0 184 328 0 0 0 0
+            PATHCONF: 1 1 0 92 140 0 0 0 0
+              COMMIT: 0 0 0 0 0 0 0 0 0
+`
 )
+
+func TestMountStatsExtendedRDMAStats(t *testing.T) {
+	r := strings.NewReader(extendedRDMAExampleALLMountstats)
+	_, err := parseMountStats(r)
+	if err != nil {
+		t.Errorf("failed to parse mount stats with extended RDMA statistics: %v", err)
+	}
+}
diff --git a/nfs/parse_nfsd_test.go b/nfs/parse_nfsd_test.go
index b09b3b5..a7e9689 100644
--- a/nfs/parse_nfsd_test.go
+++ b/nfs/parse_nfsd_test.go
@@ -33,7 +33,7 @@ func TestNewNFSdServerRPCStats(t *testing.T) {
 			content: "invalid",
 			invalid: true,
 		}, {
-			name: "good file",
+			name: "good file, proc4ops 72",
 			content: `rc 0 6 18622
 fh 0 0 0 0 0
 io 157286400 0
@@ -45,6 +45,7 @@ proc2 18 2 69 0 0 4410 0 0 0 0 0 0 0 0 0 0 0 99 2
 proc3 22 2 112 0 2719 111 0 0 0 0 0 0 0 0 0 0 0 27 216 0 2 1 0
 proc4 2 2 10853
 proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+wdeleg_getattr 16
 `,
 			stats: &nfs.ServerRPCStats{
 				ReplyCache: nfs.ReplyCache{
@@ -134,45 +135,485 @@ proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150
 					Compound: 10853,
 				},
 				V4Ops: nfs.V4Ops{
-					Op0Unused:    0,
-					Op1Unused:    0,
-					Op2Future:    0,
-					Access:       1098,
-					Close:        2,
-					Commit:       0,
-					Create:       0,
-					DelegPurge:   0,
-					DelegReturn:  0,
-					GetAttr:      8179,
-					GetFH:        5896,
-					Link:         0,
-					Lock:         0,
-					Lockt:        0,
-					Locku:        0,
-					Lookup:       5900,
-					LookupRoot:   0,
-					Nverify:      0,
-					Open:         2,
-					OpenAttr:     0,
-					OpenConfirm:  2,
-					OpenDgrd:     0,
-					PutFH:        9609,
-					PutPubFH:     0,
-					PutRootFH:    2,
-					Read:         150,
-					ReadDir:      1272,
-					ReadLink:     0,
-					Remove:       0,
-					Rename:       0,
-					Renew:        1236,
-					RestoreFH:    0,
-					SaveFH:       0,
-					SecInfo:      0,
-					SetAttr:      0,
-					Verify:       3,
-					Write:        3,
-					RelLockOwner: 0,
+					Op0Unused:          0,
+					Op1Unused:          0,
+					Op2Future:          0,
+					Access:             1098,
+					Close:              2,
+					Commit:             0,
+					Create:             0,
+					DelegPurge:         0,
+					DelegReturn:        0,
+					GetAttr:            8179,
+					GetFH:              5896,
+					Link:               0,
+					Lock:               0,
+					Lockt:              0,
+					Locku:              0,
+					Lookup:             5900,
+					LookupRoot:         0,
+					Nverify:            0,
+					Open:               2,
+					OpenAttr:           0,
+					OpenConfirm:        2,
+					OpenDgrd:           0,
+					PutFH:              9609,
+					PutPubFH:           0,
+					PutRootFH:          2,
+					Read:               150,
+					ReadDir:            1272,
+					ReadLink:           0,
+					Remove:             0,
+					Rename:             0,
+					Renew:              1236,
+					RestoreFH:          0,
+					SaveFH:             0,
+					SecInfo:            0,
+					SetAttr:            0,
+					SetClientID:        3,
+					SetClientIDConfirm: 3,
+					Verify:             0,
+					Write:              0,
+					RelLockOwner:       0,
 				},
+				WdelegGetattr: 16,
+			},
+		}, {
+			name: "good file, proc4ops 40",
+			content: `rc 0 25020854 19157796
+fh 276 0 0 0 0
+io 899844043 2470085989
+th 1024 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
+ra 2048 4250593 118232 55926 31504 20253 13815 9875 7028 5546 3991 171551
+net 44179842 1 44179026 3092
+rpc 44177753 0 0 0 0
+proc2 18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+proc3 22 747 7259760 1383711 1570520 3464939 8436 4688207 21668847 1173194 6457 2127 172 213538 1253 556401 14950 1101 56245 90790 742 367 1989658
+proc4 2 0 0
+proc4ops 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+wdeleg_getattr 9`,
+
+			stats: &nfs.ServerRPCStats{
+				ReplyCache: nfs.ReplyCache{
+					Hits:    0,
+					Misses:  25020854,
+					NoCache: 19157796,
+				},
+				FileHandles: nfs.FileHandles{
+					Stale:        276,
+					TotalLookups: 0,
+					AnonLookups:  0,
+					DirNoCache:   0,
+					NoDirNoCache: 0,
+				},
+				InputOutput: nfs.InputOutput{
+					Read:  899844043,
+					Write: 2470085989,
+				},
+				Threads: nfs.Threads{
+					Threads: 1024,
+					FullCnt: 0,
+				},
+				ReadAheadCache: nfs.ReadAheadCache{
+					CacheSize:      2048,
+					CacheHistogram: []uint64{4250593, 118232, 55926, 31504, 20253, 13815, 9875, 7028, 5546, 3991},
+					NotFound:       171551,
+				},
+				Network: nfs.Network{
+					NetCount:   44179842,
+					UDPCount:   1,
+					TCPCount:   44179026,
+					TCPConnect: 3092,
+				},
+				ServerRPC: nfs.ServerRPC{
+					RPCCount: 44177753,
+					BadCnt:   0,
+					BadFmt:   0,
+					BadAuth:  0,
+					BadcInt:  0,
+				},
+				V2Stats: nfs.V2Stats{
+					Null:     0,
+					GetAttr:  0,
+					SetAttr:  0,
+					Root:     0,
+					Lookup:   0,
+					ReadLink: 0,
+					Read:     0,
+					WrCache:  0,
+					Write:    0,
+					Create:   0,
+					Remove:   0,
+					Rename:   0,
+					Link:     0,
+					SymLink:  0,
+					MkDir:    0,
+					RmDir:    0,
+					ReadDir:  0,
+					FsStat:   0,
+				},
+				V3Stats: nfs.V3Stats{
+					Null:        747,
+					GetAttr:     7259760,
+					SetAttr:     1383711,
+					Lookup:      1570520,
+					Access:      3464939,
+					ReadLink:    8436,
+					Read:        4688207,
+					Write:       21668847,
+					Create:      1173194,
+					MkDir:       6457,
+					SymLink:     2127,
+					MkNod:       172,
+					Remove:      213538,
+					RmDir:       1253,
+					Rename:      556401,
+					Link:        14950,
+					ReadDir:     1101,
+					ReadDirPlus: 56245,
+					FsStat:      90790,
+					FsInfo:      742,
+					PathConf:    367,
+					Commit:      1989658,
+				},
+				ServerV4Stats: nfs.ServerV4Stats{
+					Null:     0,
+					Compound: 0,
+				},
+				V4Ops: nfs.V4Ops{
+					Op0Unused:          0,
+					Op1Unused:          0,
+					Op2Future:          0,
+					Access:             0,
+					Close:              0,
+					Commit:             0,
+					Create:             0,
+					DelegPurge:         0,
+					DelegReturn:        0,
+					GetAttr:            0,
+					GetFH:              0,
+					Link:               0,
+					Lock:               0,
+					Lockt:              0,
+					Locku:              0,
+					Lookup:             0,
+					LookupRoot:         0,
+					Nverify:            0,
+					Open:               0,
+					OpenAttr:           0,
+					OpenConfirm:        0,
+					OpenDgrd:           0,
+					PutFH:              0,
+					PutPubFH:           0,
+					PutRootFH:          0,
+					Read:               0,
+					ReadDir:            0,
+					ReadLink:           0,
+					Remove:             0,
+					Rename:             0,
+					Renew:              0,
+					RestoreFH:          0,
+					SaveFH:             0,
+					SecInfo:            0,
+					SetAttr:            0,
+					SetClientID:        0,
+					SetClientIDConfirm: 0,
+					Verify:             0,
+					Write:              0,
+					RelLockOwner:       0,
+				},
+				WdelegGetattr: 9,
+			},
+		},
+		{
+			name: "good file, proc4ops 59",
+			content: `rc 0 268 742119
+fh 0 0 0 0 0
+io 2476981939 0
+th 8 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
+ra 32 30104 0 0 0 0 0 0 0 0 0 71174
+net 742701 314 742393 10103960
+rpc 742406 310 310 0 0
+proc2 18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+proc3 22 105 71158 0 175642 184711 17103 101277 0 0 0 0 0 0 0 0 0 0 8916 102 202 0 0
+proc4 2 101 182991
+proc4ops 59 0 0 0 18112 8341 0 0 0 3239 71595 11834 0 0 0 0 107097 0 0 8344 0 5100 0 181968 0 235 5735 4406 0 0 0 652 8342 8344 0 0 134 134 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+wdeleg_getattr 10`,
+			stats: &nfs.ServerRPCStats{
+				ReplyCache: nfs.ReplyCache{
+					Hits:    0,
+					Misses:  268,
+					NoCache: 742119,
+				},
+				FileHandles: nfs.FileHandles{
+					Stale:        0,
+					TotalLookups: 0,
+					AnonLookups:  0,
+					DirNoCache:   0,
+					NoDirNoCache: 0,
+				},
+				InputOutput: nfs.InputOutput{
+					Read:  2476981939,
+					Write: 0,
+				},
+				Threads: nfs.Threads{
+					Threads: 8,
+					FullCnt: 0,
+				},
+				ReadAheadCache: nfs.ReadAheadCache{
+					CacheSize:      32,
+					CacheHistogram: []uint64{30104, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+					NotFound:       71174,
+				},
+				Network: nfs.Network{
+					NetCount:   742701,
+					UDPCount:   314,
+					TCPCount:   742393,
+					TCPConnect: 10103960,
+				},
+				ServerRPC: nfs.ServerRPC{
+					RPCCount: 742406,
+					BadCnt:   310,
+					BadFmt:   310,
+					BadAuth:  0,
+					BadcInt:  0,
+				},
+				V2Stats: nfs.V2Stats{
+					Null:     0,
+					GetAttr:  0,
+					SetAttr:  0,
+					Root:     0,
+					Lookup:   0,
+					ReadLink: 0,
+					Read:     0,
+					WrCache:  0,
+					Write:    0,
+					Create:   0,
+					Remove:   0,
+					Rename:   0,
+					Link:     0,
+					SymLink:  0,
+					MkDir:    0,
+					RmDir:    0,
+					ReadDir:  0,
+					FsStat:   0,
+				},
+				V3Stats: nfs.V3Stats{
+					Null:        105,
+					GetAttr:     71158,
+					SetAttr:     0,
+					Lookup:      175642,
+					Access:      184711,
+					ReadLink:    17103,
+					Read:        101277,
+					Write:       0,
+					Create:      0,
+					MkDir:       0,
+					SymLink:     0,
+					MkNod:       0,
+					Remove:      0,
+					RmDir:       0,
+					Rename:      0,
+					Link:        0,
+					ReadDir:     0,
+					ReadDirPlus: 8916,
+					FsStat:      102,
+					FsInfo:      202,
+					PathConf:    0,
+					Commit:      0,
+				},
+				ServerV4Stats: nfs.ServerV4Stats{
+					Null:     101,
+					Compound: 182991,
+				},
+				V4Ops: nfs.V4Ops{
+					Op0Unused:          0,
+					Op1Unused:          0,
+					Op2Future:          0,
+					Access:             18112,
+					Close:              8341,
+					Commit:             0,
+					Create:             0,
+					DelegPurge:         0,
+					DelegReturn:        3239,
+					GetAttr:            71595,
+					GetFH:              11834,
+					Link:               0,
+					Lock:               0,
+					Lockt:              0,
+					Locku:              0,
+					Lookup:             107097,
+					LookupRoot:         0,
+					Nverify:            0,
+					Open:               8344,
+					OpenAttr:           0,
+					OpenConfirm:        5100,
+					OpenDgrd:           0,
+					PutFH:              181968,
+					PutPubFH:           0,
+					PutRootFH:          235,
+					Read:               5735,
+					ReadDir:            4406,
+					ReadLink:           0,
+					Remove:             0,
+					Rename:             0,
+					Renew:              652,
+					RestoreFH:          8342,
+					SaveFH:             8344,
+					SecInfo:            0,
+					SetAttr:            0,
+					SetClientID:        134,
+					SetClientIDConfirm: 134,
+					Verify:             0,
+					Write:              0,
+					RelLockOwner:       0,
+				},
+				WdelegGetattr: 10,
+			},
+		}, {
+			name: "good file, proc4ops 39",
+			content: `rc 0 25020854 19157796
+fh 276 0 0 0 0
+io 899844043 2470085989
+th 1024 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
+ra 2048 4250593 118232 55926 31504 20253 13815 9875 7028 5546 3991 171551
+net 44179842 1 44179026 3092
+rpc 44177753 0 0 0 0
+proc2 18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+proc3 22 747 7259760 1383711 1570520 3464939 8436 4688207 21668847 1173194 6457 2127 172 213538 1253 556401 14950 1101 56245 90790 742 367 1989658
+proc4 2 0 0
+proc4ops 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 39
+wdeleg_getattr 765432`,
+			stats: &nfs.ServerRPCStats{
+				ReplyCache: nfs.ReplyCache{
+					Hits:    0,
+					Misses:  25020854,
+					NoCache: 19157796,
+				},
+				FileHandles: nfs.FileHandles{
+					Stale:        276,
+					TotalLookups: 0,
+					AnonLookups:  0,
+					DirNoCache:   0,
+					NoDirNoCache: 0,
+				},
+				InputOutput: nfs.InputOutput{
+					Read:  899844043,
+					Write: 2470085989,
+				},
+				Threads: nfs.Threads{
+					Threads: 1024,
+					FullCnt: 0,
+				},
+				ReadAheadCache: nfs.ReadAheadCache{
+					CacheSize:      2048,
+					CacheHistogram: []uint64{4250593, 118232, 55926, 31504, 20253, 13815, 9875, 7028, 5546, 3991},
+					NotFound:       171551,
+				},
+				Network: nfs.Network{
+					NetCount:   44179842,
+					UDPCount:   1,
+					TCPCount:   44179026,
+					TCPConnect: 3092,
+				},
+				ServerRPC: nfs.ServerRPC{
+					RPCCount: 44177753,
+					BadCnt:   0,
+					BadFmt:   0,
+					BadAuth:  0,
+					BadcInt:  0,
+				},
+				V2Stats: nfs.V2Stats{
+					Null:     0,
+					GetAttr:  0,
+					SetAttr:  0,
+					Root:     0,
+					Lookup:   0,
+					ReadLink: 0,
+					Read:     0,
+					WrCache:  0,
+					Write:    0,
+					Create:   0,
+					Remove:   0,
+					Rename:   0,
+					Link:     0,
+					SymLink:  0,
+					MkDir:    0,
+					RmDir:    0,
+					ReadDir:  0,
+					FsStat:   0,
+				},
+				V3Stats: nfs.V3Stats{
+					Null:        747,
+					GetAttr:     7259760,
+					SetAttr:     1383711,
+					Lookup:      1570520,
+					Access:      3464939,
+					ReadLink:    8436,
+					Read:        4688207,
+					Write:       21668847,
+					Create:      1173194,
+					MkDir:       6457,
+					SymLink:     2127,
+					MkNod:       172,
+					Remove:      213538,
+					RmDir:       1253,
+					Rename:      556401,
+					Link:        14950,
+					ReadDir:     1101,
+					ReadDirPlus: 56245,
+					FsStat:      90790,
+					FsInfo:      742,
+					PathConf:    367,
+					Commit:      1989658,
+				},
+				ServerV4Stats: nfs.ServerV4Stats{
+					Null:     0,
+					Compound: 0,
+				},
+				V4Ops: nfs.V4Ops{
+					Op0Unused:          0,
+					Op1Unused:          0,
+					Op2Future:          0,
+					Access:             0,
+					Close:              0,
+					Commit:             0,
+					Create:             0,
+					DelegPurge:         0,
+					DelegReturn:        0,
+					GetAttr:            0,
+					GetFH:              0,
+					Link:               0,
+					Lock:               0,
+					Lockt:              0,
+					Locku:              0,
+					Lookup:             0,
+					LookupRoot:         0,
+					Nverify:            0,
+					Open:               0,
+					OpenAttr:           0,
+					OpenConfirm:        0,
+					OpenDgrd:           0,
+					PutFH:              0,
+					PutPubFH:           0,
+					PutRootFH:          0,
+					Read:               0,
+					ReadDir:            0,
+					ReadLink:           0,
+					Remove:             0,
+					Rename:             0,
+					Renew:              0,
+					RestoreFH:          0,
+					SaveFH:             0,
+					SecInfo:            0,
+					SetAttr:            0,
+					SetClientID:        0,
+					SetClientIDConfirm: 0,
+					Verify:             0,
+					Write:              39,
+					RelLockOwner:       0,
+				},
+				WdelegGetattr: 765432,
 			},
 		},
 	}
diff --git a/proc_maps64_test.go b/proc_maps64_test.go
index 66ef134..44ec68c 100644
--- a/proc_maps64_test.go
+++ b/proc_maps64_test.go
@@ -138,3 +138,40 @@ func TestProcMaps(t *testing.T) {
 	}
 
 }
+
+var start, end uintptr
+
+func BenchmarkParseAddress(b *testing.B) {
+	b.ReportAllocs()
+	var (
+		s, e uintptr
+		err  error
+	)
+	for i := 0; i < b.N; i++ {
+		s, e, err = parseAddresses("7f7d7469e000-7f7d746a0000")
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+	// Prevent the compiler from optimizing away benchmark code.
+	start = s
+	end = e
+}
+
+var device uint64
+
+func BenchmarkParseDevice(b *testing.B) {
+	b.ReportAllocs()
+	var (
+		d   uint64
+		err error
+	)
+	for i := 0; i < b.N; i++ {
+		d, err = parseDevice("00:22")
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+	// Prevent the compiler from optimizing away benchmark code.
+	device = d
+}
diff --git a/proc_status_test.go b/proc_status_test.go
index 2f0884f..9d22a48 100644
--- a/proc_status_test.go
+++ b/proc_status_test.go
@@ -36,6 +36,7 @@ func TestProcStatus(t *testing.T) {
 	}{
 		{name: "Pid", want: 26231, have: s.PID},
 		{name: "Tgid", want: 26231, have: s.TGID},
+		{name: "NSpid", want: 1, have: int(s.NSpids[0])},
 		{name: "VmPeak", want: 58472 * 1024, have: int(s.VmPeak)},
 		{name: "VmSize", want: 58440 * 1024, have: int(s.VmSize)},
 		{name: "VmLck", want: 0 * 1024, have: int(s.VmLck)},
diff --git a/sysfs/vulnerability_test.go b/sysfs/vulnerability_test.go
index 255586d..ec9cafc 100644
--- a/sysfs/vulnerability_test.go
+++ b/sysfs/vulnerability_test.go
@@ -38,14 +38,15 @@ func TestFS_CPUVulnerabilities(t *testing.T) {
 		want              *Vulnerability
 		wantErr           bool
 	}{
-		{"Not affected", "itlb_multihit", &Vulnerability{CodeName: "itlb_multihit", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
-		{"Not affected with underscores", "tsx_async_abort", &Vulnerability{CodeName: "tsx_async_abort", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
+		{"Not affected", "tsx_async_abort", &Vulnerability{CodeName: "tsx_async_abort", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
 		{"Mitigation simple string", "spec_store_bypass", &Vulnerability{CodeName: "spec_store_bypass", State: VulnerabilityStateMitigation, Mitigation: "Speculative Store Bypass disabled via prctl"}, false},
 		{"Mitigation special chars", "retbleed", &Vulnerability{CodeName: "retbleed", State: VulnerabilityStateMitigation, Mitigation: "untrained return thunk; SMT enabled with STIBP protection"}, false},
 		{"Mitigation more special chars", "spectre_v1", &Vulnerability{CodeName: "spectre_v1", State: VulnerabilityStateMitigation, Mitigation: "usercopy/swapgs barriers and __user pointer sanitization"}, false},
 		{"Mitigation with multiple subsections", "spectre_v2", &Vulnerability{CodeName: "spectre_v2", State: VulnerabilityStateMitigation, Mitigation: "Retpolines, IBPB: conditional, STIBP: always-on, RSB filling, PBRSB-eIBRS: Not affected"}, false},
 		{"Vulnerable", "mds", &Vulnerability{CodeName: "mds", State: VulnerabilityStateVulnerable, Mitigation: ""}, false},
 		{"Vulnerable with mitigation available", "mmio_stale_data", &Vulnerability{CodeName: "mmio_stale_data", State: VulnerabilityStateVulnerable, Mitigation: "Clear CPU buffers attempted, no microcode"}, false},
+		{"Unknown", "srbds", &Vulnerability{CodeName: "srbds", State: VulnerabilityStateUnknown, Mitigation: "Dependent on hypervisor status"}, false},
+		{"Unknown with unparseable mitigation", "itlb_multihit", &Vulnerability{CodeName: "itlb_multihit", State: VulnerabilityStateUnknown, Mitigation: "KVM: Mitigation: VMX unsupported"}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index ae2e0f0..39ec946 100644
--- a/testdata/fixtures.ttar
+++ b/testdata/fixtures.ttar
@@ -13239,7 +13239,7 @@ Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/itlb_multihit
 Lines: 1
-Not affected
+KVM: Mitigation: VMX unsupported
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/mds
@@ -13272,6 +13272,11 @@ Lines: 1
 Mitigation: Retpolines, IBPB: conditional, STIBP: always-on, RSB filling, PBRSB-eIBRS: Not affected
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/vulnerabilities/srbds
+Lines: 1
+Unknown: Dependent on hypervisor status
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/tsx_async_abort
 Lines: 1
 Not affected