Repository: prometheus/procfs
Pull request #187 (2019-06-26 08:24:29)

Fix crash if port_rcv_data contains "N/A (no PMA)"
In case `port_rcv_data` contains `N/A (no PMA)`, `vp.PUInt64` returns `nil` which is assigned to `counters.PortRcvData`. Then the content of `counters.PortRcvData` cannot be multiplied by four. The program will fail with:

```
panic: runtime error: invalid memory address or nil pointer dereference
```

This issue was found by the tests in https://github.com/prometheus/node_exporter/pull/1396

discordianfish: Wondering if we could handle that better in the value parser but possibly not, so LGTM either way.
bdrung: Alternatively the value parser could accept a function that it applies on the value before returning it.

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index 6b42e7b..8ebd5b9 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -47,6 +47,48 @@ SymlinkTo: ../../symlinktargets/ghi
 Path: fixtures/proc/26231/fd/3
 SymlinkTo: ../../symlinktargets/uvw
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/proc/26231/fdinfo
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/fdinfo/0
+Lines: 6
+pos:	0
+flags:	02004000
+mnt_id:	13
+inotify wd:3 ino:1 sdev:34 mask:fce ignored_mask:0 fhandle-bytes:c fhandle-type:81 f_handle:000000000100000000000000
+inotify wd:2 ino:1300016 sdev:fd00002 mask:fce ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:16003001ed3f022a
+inotify wd:1 ino:2e0001 sdev:fd00000 mask:fce ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:01002e00138e7c65
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/fdinfo/1
+Lines: 4
+pos:	0
+flags:	02004002
+mnt_id:	13
+eventfd-count:                0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/fdinfo/10
+Lines: 3
+pos:	0
+flags:	02004002
+mnt_id:	9
+Mode: 400
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/fdinfo/2
+Lines: 3
+pos:	0
+flags:	02004002
+mnt_id:	9
+Mode: 400
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/fdinfo/3
+Lines: 3
+pos:	0
+flags:	02004002
+mnt_id:	9
+Mode: 400
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26231/io
 Lines: 7
 rchar: 750339
@@ -619,6 +661,227 @@ Path: fixtures/proc/symlinktargets/xyz
 Lines: 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/proc/sys
+Mode: 775
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/proc/sys/vm
+Mode: 775
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/admin_reserve_kbytes
+Lines: 1
+8192
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/block_dump
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/compact_unevictable_allowed
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_background_bytes
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_background_ratio
+Lines: 1
+10
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_bytes
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_expire_centisecs
+Lines: 1
+3000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_ratio
+Lines: 1
+20
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirty_writeback_centisecs
+Lines: 1
+500
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/dirtytime_expire_seconds
+Lines: 1
+43200
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/drop_caches
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/extfrag_threshold
+Lines: 1
+500
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/hugetlb_shm_group
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/laptop_mode
+Lines: 1
+5
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/legacy_va_layout
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/lowmem_reserve_ratio
+Lines: 1
+256	256	32	0	0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/max_map_count
+Lines: 1
+65530
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/memory_failure_early_kill
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/memory_failure_recovery
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/min_free_kbytes
+Lines: 1
+67584
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/min_slab_ratio
+Lines: 1
+5
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/min_unmapped_ratio
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/mmap_min_addr
+Lines: 1
+65536
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/nr_hugepages
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/nr_hugepages_mempolicy
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/nr_overcommit_hugepages
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/numa_stat
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/numa_zonelist_order
+Lines: 1
+Node
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/oom_dump_tasks
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/oom_kill_allocating_task
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/overcommit_kbytes
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/overcommit_memory
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/overcommit_ratio
+Lines: 1
+50
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/page-cluster
+Lines: 1
+3
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/panic_on_oom
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/percpu_pagelist_fraction
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/stat_interval
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/swappiness
+Lines: 1
+60
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/user_reserve_kbytes
+Lines: 1
+131072
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/vfs_cache_pressure
+Lines: 1
+100
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/watermark_boost_factor
+Lines: 1
+15000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/watermark_scale_factor
+Lines: 1
+10
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/sys/vm/zone_reclaim_mode
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/proc.go b/proc.go
index 41c148d..a0aee59 100644
--- a/proc.go
+++ b/proc.go
@@ -279,3 +279,24 @@ func (p Proc) fileDescriptors() ([]string, error) {
 func (p Proc) path(pa ...string) string {
 	return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
 }
+
+// FileDescriptorsInfo retrieves information about all file descriptors of
+// the process.
+func (p Proc) FileDescriptorsInfo() (ProcFDInfos, error) {
+	names, err := p.fileDescriptors()
+	if err != nil {
+		return nil, err
+	}
+
+	var fdinfos ProcFDInfos
+
+	for _, n := range names {
+		fdinfo, err := p.FDInfo(n)
+		if err != nil {
+			continue
+		}
+		fdinfos = append(fdinfos, *fdinfo)
+	}
+
+	return fdinfos, nil
+}
diff --git a/proc_fdinfo.go b/proc_fdinfo.go
new file mode 100644
index 0000000..83b67d1
--- /dev/null
+++ b/proc_fdinfo.go
@@ -0,0 +1,132 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"bufio"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strings"
+)
+
+// Regexp variables
+var (
+	rPos     = regexp.MustCompile(`^pos:\s+(\d+)$`)
+	rFlags   = regexp.MustCompile(`^flags:\s+(\d+)$`)
+	rMntID   = regexp.MustCompile(`^mnt_id:\s+(\d+)$`)
+	rInotify = regexp.MustCompile(`^inotify`)
+)
+
+// ProcFDInfo contains represents file descriptor information.
+type ProcFDInfo struct {
+	// File descriptor
+	FD string
+	// File offset
+	Pos string
+	// File access mode and status flags
+	Flags string
+	// Mount point ID
+	MntID string
+	// List of inotify lines (structed) in the fdinfo file (kernel 3.8+ only)
+	InotifyInfos []InotifyInfo
+}
+
+// FDInfo constructor. On kernels older than 3.8, InotifyInfos will always be empty.
+func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
+	f, err := os.Open(p.path("fdinfo", fd))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	fdinfo, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, fmt.Errorf("could not read %s: %s", f.Name(), err)
+	}
+
+	var text, pos, flags, mntid string
+	var inotify []InotifyInfo
+
+	scanner := bufio.NewScanner(strings.NewReader(string(fdinfo)))
+	for scanner.Scan() {
+		text = scanner.Text()
+		if rPos.MatchString(text) {
+			pos = rPos.FindStringSubmatch(text)[1]
+		} else if rFlags.MatchString(text) {
+			flags = rFlags.FindStringSubmatch(text)[1]
+		} else if rMntID.MatchString(text) {
+			mntid = rMntID.FindStringSubmatch(text)[1]
+		} else if rInotify.MatchString(text) {
+			newInotify, err := parseInotifyInfo(text)
+			if err != nil {
+				return nil, err
+			}
+			inotify = append(inotify, *newInotify)
+		}
+	}
+
+	i := &ProcFDInfo{
+		FD:           fd,
+		Pos:          pos,
+		Flags:        flags,
+		MntID:        mntid,
+		InotifyInfos: inotify,
+	}
+
+	return i, nil
+}
+
+// InotifyInfo represents a single inotify line in the fdinfo file.
+type InotifyInfo struct {
+	// Watch descriptor number
+	WD string
+	// Inode number
+	Ino string
+	// Device ID
+	Sdev string
+	// Mask of events being monitored
+	Mask string
+}
+
+// InotifyInfo constructor. Only available on kernel 3.8+.
+func parseInotifyInfo(line string) (*InotifyInfo, error) {
+	r := regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)\s+mask:([0-9a-f]+)`)
+	m := r.FindStringSubmatch(line)
+	i := &InotifyInfo{
+		WD:   m[1],
+		Ino:  m[2],
+		Sdev: m[3],
+		Mask: m[4],
+	}
+	return i, nil
+}
+
+// ProcFDInfos represents a list of ProcFDInfo structs.
+type ProcFDInfos []ProcFDInfo
+
+func (p ProcFDInfos) Len() int           { return len(p) }
+func (p ProcFDInfos) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+func (p ProcFDInfos) Less(i, j int) bool { return p[i].FD < p[j].FD }
+
+// InotifyWatchLen returns the total number of inotify watches
+func (p ProcFDInfos) InotifyWatchLen() (int, error) {
+	length := 0
+	for _, f := range p {
+		length += len(f.InotifyInfos)
+	}
+
+	return length, nil
+}
diff --git a/sysfs/class_infiniband.go b/sysfs/class_infiniband.go
index 2600083..e81cb85 100644
--- a/sysfs/class_infiniband.go
+++ b/sysfs/class_infiniband.go
@@ -275,7 +275,9 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 			counters.PortRcvConstraintErrors = vp.PUInt64()
 		case "port_rcv_data":
 			counters.PortRcvData = vp.PUInt64()
-			*counters.PortRcvData *= 4
+			if counters.PortRcvData != nil {
+				*counters.PortRcvData *= 4
+			}
 		case "port_rcv_discards":
 			counters.PortRcvDiscards = vp.PUInt64()
 		case "port_rcv_errors":
@@ -286,7 +288,9 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 			counters.PortXmitConstraintErrors = vp.PUInt64()
 		case "port_xmit_data":
 			counters.PortXmitData = vp.PUInt64()
-			*counters.PortXmitData *= 4
+			if counters.PortXmitData != nil {
+				*counters.PortXmitData *= 4
+			}
 		case "port_xmit_discards":
 			counters.PortXmitDiscards = vp.PUInt64()
 		case "port_xmit_packets":
@@ -339,7 +343,9 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 			counters.LegacyPortMulticastXmitPackets = vp.PUInt64()
 		case "port_rcv_data_64":
 			counters.LegacyPortRcvData64 = vp.PUInt64()
-			*counters.LegacyPortRcvData64 *= 4
+			if counters.LegacyPortRcvData64 != nil {
+				*counters.LegacyPortRcvData64 *= 4
+			}
 		case "port_rcv_packets_64":
 			counters.LegacyPortRcvPackets64 = vp.PUInt64()
 		case "port_unicast_rcv_packets":
@@ -348,7 +354,9 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 			counters.LegacyPortUnicastXmitPackets = vp.PUInt64()
 		case "port_xmit_data_64":
 			counters.LegacyPortXmitData64 = vp.PUInt64()
-			*counters.LegacyPortXmitData64 *= 4
+			if counters.LegacyPortXmitData64 != nil {
+				*counters.LegacyPortXmitData64 *= 4
+			}
 		case "port_xmit_packets_64":
 			counters.LegacyPortXmitPackets64 = vp.PUInt64()
 		}
diff --git a/vm.go b/vm.go
new file mode 100644
index 0000000..f22651c
--- /dev/null
+++ b/vm.go
@@ -0,0 +1,210 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package procfs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// The VM interface is described at
+//   https://www.kernel.org/doc/Documentation/sysctl/vm.txt
+// Each setting is exposed as a single file.
+// Each file contains one line with a single numerical value, except lowmem_reserve_ratio which holds an array
+// and numa_zonelist_order (deprecated) which is a string
+type VM struct {
+	AdminReserveKbytes        int64   // /proc/sys/vm/admin_reserve_kbytes
+	BlockDump                 int64   // /proc/sys/vm/block_dump
+	CompactUnevictableAllowed int64   // /proc/sys/vm/compact_unevictable_allowed
+	DirtyBackgroundBytes      int64   // /proc/sys/vm/dirty_background_bytes
+	DirtyBackgroundRatio      int64   // /proc/sys/vm/dirty_background_ratio
+	DirtyBytes                int64   // /proc/sys/vm/dirty_bytes
+	DirtyExpireCentisecs      int64   // /proc/sys/vm/dirty_expire_centisecs
+	DirtyRatio                int64   // /proc/sys/vm/dirty_ratio
+	DirtytimeExpireSeconds    int64   // /proc/sys/vm/dirtytime_expire_seconds
+	DirtyWritebackCentisecs   int64   // /proc/sys/vm/dirty_writeback_centisecs
+	DropCaches                int64   // /proc/sys/vm/drop_caches
+	ExtfragThreshold          int64   // /proc/sys/vm/extfrag_threshold
+	HugetlbShmGroup           int64   // /proc/sys/vm/hugetlb_shm_group
+	LaptopMode                int64   // /proc/sys/vm/laptop_mode
+	LegacyVaLayout            int64   // /proc/sys/vm/legacy_va_layout
+	LowmemReserveRatio        []int64 // /proc/sys/vm/lowmem_reserve_ratio
+	MaxMapCount               int64   // /proc/sys/vm/max_map_count
+	MemoryFailureEarlyKill    int64   // /proc/sys/vm/memory_failure_early_kill
+	MemoryFailureRecovery     int64   // /proc/sys/vm/memory_failure_recovery
+	MinFreeKbytes             int64   // /proc/sys/vm/min_free_kbytes
+	MinSlabRatio              int64   // /proc/sys/vm/min_slab_ratio
+	MinUnmappedRatio          int64   // /proc/sys/vm/min_unmapped_ratio
+	MmapMinAddr               int64   // /proc/sys/vm/mmap_min_addr
+	NrHugepages               int64   // /proc/sys/vm/nr_hugepages
+	NrHugepagesMempolicy      int64   // /proc/sys/vm/nr_hugepages_mempolicy
+	NrOvercommitHugepages     int64   // /proc/sys/vm/nr_overcommit_hugepages
+	NumaStat                  int64   // /proc/sys/vm/numa_stat
+	NumaZonelistOrder         string  // /proc/sys/vm/numa_zonelist_order
+	OomDumpTasks              int64   // /proc/sys/vm/oom_dump_tasks
+	OomKillAllocatingTask     int64   // /proc/sys/vm/oom_kill_allocating_task
+	OvercommitKbytes          int64   // /proc/sys/vm/overcommit_kbytes
+	OvercommitMemory          int64   // /proc/sys/vm/overcommit_memory
+	OvercommitRatio           int64   // /proc/sys/vm/overcommit_ratio
+	PageCluster               int64   // /proc/sys/vm/page-cluster
+	PanicOnOom                int64   // /proc/sys/vm/panic_on_oom
+	PercpuPagelistFraction    int64   // /proc/sys/vm/percpu_pagelist_fraction
+	StatInterval              int64   // /proc/sys/vm/stat_interval
+	Swappiness                int64   // /proc/sys/vm/swappiness
+	UserReserveKbytes         int64   // /proc/sys/vm/user_reserve_kbytes
+	VfsCachePressure          int64   // /proc/sys/vm/vfs_cache_pressure
+	WatermarkBoostFactor      int64   // /proc/sys/vm/watermark_boost_factor
+	WatermarkScaleFactor      int64   // /proc/sys/vm/watermark_scale_factor
+	ZoneReclaimMode           int64   // /proc/sys/vm/zone_reclaim_mode
+}
+
+// VM reads the VM statistics from the specified `proc` filesystem.
+func (fs FS) VM() (*VM, error) {
+	path := fs.proc.Path("sys/vm")
+	file, err := os.Stat(path)
+	if err != nil {
+		return nil, err
+	}
+	if !file.Mode().IsDir() {
+		return nil, fmt.Errorf("%s is not a directory", path)
+	}
+
+	files, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+
+	var vm VM
+	for _, f := range files {
+		if f.IsDir() {
+			continue
+		}
+
+		name := filepath.Join(path, f.Name())
+		// ignore errors on read, as there are some write only
+		// in /proc/sys/vm
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			continue
+		}
+		vp := util.NewValueParser(value)
+
+		switch f.Name() {
+		case "admin_reserve_kbytes":
+			vm.AdminReserveKbytes = *vp.PInt64()
+		case "block_dump":
+			vm.BlockDump = *vp.PInt64()
+		case "compact_unevictable_allowed":
+			vm.CompactUnevictableAllowed = *vp.PInt64()
+		case "dirty_background_bytes":
+			vm.DirtyBackgroundBytes = *vp.PInt64()
+		case "dirty_background_ratio":
+			vm.DirtyBackgroundRatio = *vp.PInt64()
+		case "dirty_bytes":
+			vm.DirtyBytes = *vp.PInt64()
+		case "dirty_expire_centisecs":
+			vm.DirtyExpireCentisecs = *vp.PInt64()
+		case "dirty_ratio":
+			vm.DirtyRatio = *vp.PInt64()
+		case "dirtytime_expire_seconds":
+			vm.DirtytimeExpireSeconds = *vp.PInt64()
+		case "dirty_writeback_centisecs":
+			vm.DirtyWritebackCentisecs = *vp.PInt64()
+		case "drop_caches":
+			vm.DropCaches = *vp.PInt64()
+		case "extfrag_threshold":
+			vm.ExtfragThreshold = *vp.PInt64()
+		case "hugetlb_shm_group":
+			vm.HugetlbShmGroup = *vp.PInt64()
+		case "laptop_mode":
+			vm.LaptopMode = *vp.PInt64()
+		case "legacy_va_layout":
+			vm.LegacyVaLayout = *vp.PInt64()
+		case "lowmem_reserve_ratio":
+			stringSlice := strings.Fields(value)
+			int64Slice := make([]int64, 0, len(stringSlice))
+			for _, value := range stringSlice {
+				vp := util.NewValueParser(value)
+				int64Slice = append(int64Slice, *vp.PInt64())
+			}
+			vm.LowmemReserveRatio = int64Slice
+		case "max_map_count":
+			vm.MaxMapCount = *vp.PInt64()
+		case "memory_failure_early_kill":
+			vm.MemoryFailureEarlyKill = *vp.PInt64()
+		case "memory_failure_recovery":
+			vm.MemoryFailureRecovery = *vp.PInt64()
+		case "min_free_kbytes":
+			vm.MinFreeKbytes = *vp.PInt64()
+		case "min_slab_ratio":
+			vm.MinSlabRatio = *vp.PInt64()
+		case "min_unmapped_ratio":
+			vm.MinUnmappedRatio = *vp.PInt64()
+		case "mmap_min_addr":
+			vm.MmapMinAddr = *vp.PInt64()
+		case "nr_hugepages":
+			vm.NrHugepages = *vp.PInt64()
+		case "nr_hugepages_mempolicy":
+			vm.NrHugepagesMempolicy = *vp.PInt64()
+		case "nr_overcommit_hugepages":
+			vm.NrOvercommitHugepages = *vp.PInt64()
+		case "numa_stat":
+			vm.NumaStat = *vp.PInt64()
+		case "numa_zonelist_order":
+			vm.NumaZonelistOrder = value
+		case "oom_dump_tasks":
+			vm.OomDumpTasks = *vp.PInt64()
+		case "oom_kill_allocating_task":
+			vm.OomKillAllocatingTask = *vp.PInt64()
+		case "overcommit_kbytes":
+			vm.OvercommitKbytes = *vp.PInt64()
+		case "overcommit_memory":
+			vm.OvercommitMemory = *vp.PInt64()
+		case "overcommit_ratio":
+			vm.OvercommitRatio = *vp.PInt64()
+		case "page-cluster":
+			vm.PageCluster = *vp.PInt64()
+		case "panic_on_oom":
+			vm.PanicOnOom = *vp.PInt64()
+		case "percpu_pagelist_fraction":
+			vm.PercpuPagelistFraction = *vp.PInt64()
+		case "stat_interval":
+			vm.StatInterval = *vp.PInt64()
+		case "swappiness":
+			vm.Swappiness = *vp.PInt64()
+		case "user_reserve_kbytes":
+			vm.UserReserveKbytes = *vp.PInt64()
+		case "vfs_cache_pressure":
+			vm.VfsCachePressure = *vp.PInt64()
+		case "watermark_boost_factor":
+			vm.WatermarkBoostFactor = *vp.PInt64()
+		case "watermark_scale_factor":
+			vm.WatermarkScaleFactor = *vp.PInt64()
+		case "zone_reclaim_mode":
+			vm.ZoneReclaimMode = *vp.PInt64()
+		}
+		if err := vp.Err(); err != nil {
+			return nil, err
+		}
+	}
+
+	return &vm, nil
+}


Test patch:
diff --git a/proc_fdinfo_test.go b/proc_fdinfo_test.go
new file mode 100644
index 0000000..57fe213
--- /dev/null
+++ b/proc_fdinfo_test.go
@@ -0,0 +1,34 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import "testing"
+
+func TestInotifyWatchLen(t *testing.T) {
+	p1, err := getProcFixtures(t).Proc(26231)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fdinfos, err := p1.FileDescriptorsInfo()
+	if err != nil {
+		t.Fatal(err)
+	}
+	l, err := fdinfos.InotifyWatchLen()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want, have := 3, l; want != have {
+		t.Errorf("want length %d, have %d", want, have)
+	}
+}
diff --git a/proc_test.go b/proc_test.go
index f05c14f..60279fd 100644
--- a/proc_test.go
+++ b/proc_test.go
@@ -223,6 +223,32 @@ func TestFileDescriptorsLen(t *testing.T) {
 	}
 }
 
+func TestFileDescriptorsInfo(t *testing.T) {
+	p1, err := getProcFixtures(t).Proc(26231)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fdinfos, err := p1.FileDescriptorsInfo()
+	if err != nil {
+		t.Fatal(err)
+	}
+	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"},
+		}},
+		ProcFDInfo{FD: "1", Pos: "0", Flags: "02004002", MntID: "13", InotifyInfos: nil},
+		ProcFDInfo{FD: "10", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
+		ProcFDInfo{FD: "2", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
+		ProcFDInfo{FD: "3", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
+	}
+	if !reflect.DeepEqual(want, fdinfos) {
+		t.Errorf("want fdinfos %+v, have %+v", want, fdinfos)
+	}
+}
+
 type byUintptr []uintptr
 
 func (a byUintptr) Len() int           { return len(a) }
diff --git a/vm_test.go b/vm_test.go
new file mode 100644
index 0000000..492f166
--- /dev/null
+++ b/vm_test.go
@@ -0,0 +1,64 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestVM(t *testing.T) {
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+	stats, err := fs.VM()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	lowmemreserveratio := []int64{256, 256, 32, 0, 0}
+	vm := &VM{
+		AdminReserveKbytes:        8192,
+		CompactUnevictableAllowed: 1,
+		DirtyBackgroundRatio:      10,
+		DirtyExpireCentisecs:      3000,
+		DirtyRatio:                20,
+		DirtytimeExpireSeconds:    43200,
+		DirtyWritebackCentisecs:   500,
+		ExtfragThreshold:          500,
+		LaptopMode:                5,
+		LowmemReserveRatio:        lowmemreserveratio,
+		MaxMapCount:               65530,
+		MemoryFailureRecovery:     1,
+		MinFreeKbytes:             67584,
+		MinSlabRatio:              5,
+		MinUnmappedRatio:          1,
+		MmapMinAddr:               65536,
+		NumaStat:                  1,
+		NumaZonelistOrder:         "Node",
+		OomDumpTasks:              1,
+		OvercommitRatio:           50,
+		PageCluster:               3,
+		StatInterval:              1,
+		Swappiness:                60,
+		UserReserveKbytes:         131072,
+		VfsCachePressure:          100,
+		WatermarkBoostFactor:      15000,
+		WatermarkScaleFactor:      10,
+	}
+	if !reflect.DeepEqual(vm, stats) {
+		t.Errorf("Result not correct: want %v\n, have %v", vm, stats)
+	}
+}