Repository: prometheus/procfs
Pull request #176 (2019-06-10 11:44:10)

procfs: add parsing of sys/vm
Read data from /proc/sys/vm entries
Closes: #157


Theres still work to be done for `lowmem_reserve_ratio`, actually i don't understand the format yet fully especially when dealing with NUMA zones.

TheMeier:  @pgier i changed the MR using the same pattern. I used your util ValueParser.Pint64 although I don't use Pointers here, I wonder if that should be changed also.
pgier: The fixtures file ordering should be fixed by #185
TheMeier: @pgier hm fair question the documentation at https://www.kernel.org/doc/Documentation/sysctl/vm.txt doesn't really say anything about data format. 
Looking around a bit in the kernel source i found a bunch of the settings defined as `CTL_INT` https://github.com/torvalds/linux/blob/f1c2f8857c5aa6c92aa903bc06437503422e5925/kernel/sysctl_binary.c#L147-L174 which is a define to bin_intvec which returns ssize_t so those should be fine.
Some others i randomly checked are defined as int ot u64.
My intuition is it should be quite safe, but to really answer the question someone (me?) should check the definition of each setting in the kernel sources.

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/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)
+	}
+}