Repository: prometheus/procfs (go)

Issue:
Add support for parsing cgroup
We've encountered a case where we needed to know wether a particular process was running inside a container or not. One way to achieve this is to check for cgroups inheritance from the container runtime via `/proc/<pid>/cgroup`.

It would be great to have visibility on the hierarchical dependencies per cgroup resource controller.

Pull request:
Parse /proc/<pid>/cgroup file
This is a quick patch to support parsing the per-process cgroup file. Should work OK on both cgroups-v1 and cgroups-v2. 

There is one TODO in the code I could use some feedback on :-) 

I've intentionally not gone too crazy in adding this since it's my first contrib. Future work would be to locate the full path to the cgroup folder per each `Cgroup` struct (this could be accomplished by reading the mount point for that controller hierarchy and then prefixing this to the parsed Path), and also reading the actual subsystem-specific files. I do have some code to handle these later issues but I wanted to get one small change through review before biting off more 

fixes #201 


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 0aa09ed..7c4ce1f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,4 +1,4 @@
----
 linters:
   enable:
-  - golint
+  - staticcheck
+  - govet
diff --git a/Makefile.common b/Makefile.common
index b978dfc..066866e 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -69,15 +69,6 @@ else
 	GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
 endif
 
-GOTEST := $(GO) test
-GOTEST_DIR :=
-ifneq ($(CIRCLE_JOB),)
-ifneq ($(shell which gotestsum),)
-	GOTEST_DIR := test-results
-	GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
-endif
-endif
-
 PROMU_VERSION ?= 0.5.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
@@ -151,17 +142,14 @@ else
 endif
 
 .PHONY: common-test-short
-common-test-short: $(GOTEST_DIR)
+common-test-short:
 	@echo ">> running short tests"
-	GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
+	GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
 
 .PHONY: common-test
-common-test: $(GOTEST_DIR)
+common-test:
 	@echo ">> running all tests"
-	GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
-
-$(GOTEST_DIR):
-	@mkdir -p $@
+	GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs)
 
 .PHONY: common-format
 common-format:
diff --git a/bcache/bcache.go b/bcache/bcache.go
index df724ed..1db178c 100644
--- a/bcache/bcache.go
+++ b/bcache/bcache.go
@@ -29,7 +29,7 @@ type Stats struct {
 }
 
 // BcacheStats contains statistics tied to a bcache ID.
-type BcacheStats struct { // nolint:golint
+type BcacheStats struct {
 	AverageKeySize        uint64
 	BtreeCacheSize        uint64
 	CacheAvailablePercent uint64
diff --git a/bcache/get.go b/bcache/get.go
index 67fcf8b..a8896bd 100644
--- a/bcache/get.go
+++ b/bcache/get.go
@@ -51,21 +51,8 @@ func NewFS(mountPoint string) (FS, error) {
 	return FS{&fs}, nil
 }
 
-// Stats is a wrapper around stats()
-// It returns full available statistics
+// Stats retrieves bcache runtime statistics for each bcache.
 func (fs FS) Stats() ([]*Stats, error) {
-	return fs.stats(true)
-}
-
-// StatsWithoutPriority is a wrapper around stats().
-// It ignores priority_stats file, because it is expensive to read.
-func (fs FS) StatsWithoutPriority() ([]*Stats, error) {
-	return fs.stats(false)
-}
-
-// stats() retrieves bcache runtime statistics for each bcache.
-// priorityStats flag controls if we need to read priority_stats.
-func (fs FS) stats(priorityStats bool) ([]*Stats, error) {
 	matches, err := filepath.Glob(fs.sys.Path("fs/bcache/*-*"))
 	if err != nil {
 		return nil, err
@@ -77,7 +64,7 @@ func (fs FS) stats(priorityStats bool) ([]*Stats, error) {
 		name := filepath.Base(uuidPath)
 
 		// stats
-		s, err := GetStats(uuidPath, priorityStats)
+		s, err := GetStats(uuidPath)
 		if err != nil {
 			return nil, err
 		}
@@ -264,7 +251,7 @@ func (p *parser) getPriorityStats() PriorityStats {
 }
 
 // GetStats collects from sysfs files data tied to one bcache ID.
-func GetStats(uuidPath string, priorityStats bool) (*Stats, error) {
+func GetStats(uuidPath string) (*Stats, error) {
 	var bs Stats
 
 	par := parser{uuidPath: uuidPath}
@@ -383,10 +370,8 @@ func GetStats(uuidPath string, priorityStats bool) (*Stats, error) {
 		cs.MetadataWritten = par.readValue("metadata_written")
 		cs.Written = par.readValue("written")
 
-		if priorityStats {
-			ps := par.getPriorityStats()
-			cs.Priority = ps
-		}
+		ps := par.getPriorityStats()
+		cs.Priority = ps
 	}
 
 	if par.err != nil {
diff --git a/fixtures.ttar b/fixtures.ttar
index 45a7321..a21ccad 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -289,19 +289,6 @@ Max realtime priority     0                    0
 Max realtime timeout      unlimited            unlimited            us
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/26232/maps
-Lines: 9
-55680ae1e000-55680ae20000 r--p 00000000 fd:01 47316994                   /bin/cat
-55680ae29000-55680ae2a000 rwxs 0000a000 fd:01 47316994                   /bin/cat
-55680bed6000-55680bef7000 rw-p 00000000 00:00 0                          [heap]
-7fdf964fc000-7fdf973f2000 r--p 00000000 fd:01 17432624                   /usr/lib/locale/locale-archive
-7fdf973f2000-7fdf97417000 r--p 00000000 fd:01 60571062                   /lib/x86_64-linux-gnu/libc-2.29.so
-7ffe9215c000-7ffe9217f000 rw-p 00000000 00:00 0                          [stack]
-7ffe921da000-7ffe921dd000 r--p 00000000 00:00 0                          [vvar]
-7ffe921dd000-7ffe921de000 r-xp 00000000 00:00 0                          [vdso]
-ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
-Mode: 644
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26232/root
 SymlinkTo: /does/not/exist
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -330,17 +317,6 @@ Lines: 8
                 ||     ||
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Directory: fixtures/proc/26234
-Mode: 755
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/26234/maps
-Lines: 4
-08048000-08089000 r-xp 00000000 03:01 104219 /bin/tcsh
-08089000-0808c000 rw-p 00041000 03:01 104219 /bin/tcsh
-0808c000-08146000 rwxp 00000000 00:00 0
-40000000-40015000 r-xp 00000000 03:01 61874 /lib/ld-2.3.2.so
-Mode: 644
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/584
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1551,6 +1527,11 @@ blocksize    : 16
 min keysize  : 16
 max keysize  : 32
 
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/loadavg
+Lines: 1
+0.02 0.04 0.05 1/497 11947
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/diskstats
@@ -1639,11 +1620,6 @@ xpc 399724544 92823103 86219234
 debug 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/loadavg
-Lines: 1
-0.02 0.04 0.05 1/497 11947
-Mode: 444
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/mdstat
 Lines: 56
 Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
@@ -1851,14 +1827,8 @@ FRAG6: inuse 0 memory 0
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/softnet_stat
-Lines: 2
-00015c73 00020e76 F0000769 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
-01663fb2 00000000 000109a4 00000000 00000000 00000000 00000000 00000000 00000000
-Mode: 644
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/net/softnet_stat.broken
 Lines: 1
-00015c73 00020e76 F0000769 00000000
+00015c73 00020e76 F0000769 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp
diff --git a/go.mod b/go.mod
index ded4825..0e04e5d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,4 @@ go 1.12
 require (
 	github.com/google/go-cmp v0.3.1
 	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
-	golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e
 )
diff --git a/go.sum b/go.sum
index 54b5f33..33b824b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,5 +2,3 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/mountinfo.go b/mountinfo.go
index 9471136..bb01bb5 100644
--- a/mountinfo.go
+++ b/mountinfo.go
@@ -29,10 +29,10 @@ import (
 // is described in the following man page.
 // http://man7.org/linux/man-pages/man5/proc.5.html
 type MountInfo struct {
-	// Unique ID for the mount
-	MountID int
-	// The ID of the parent mount
-	ParentID int
+	// Unique Id for the mount
+	MountId int
+	// The Id of the parent mount
+	ParentId int
 	// The value of `st_dev` for the files on this FS
 	MajorMinorVer string
 	// The pathname of the directory in the FS that forms
@@ -96,11 +96,11 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
 		SuperOptions:   mountOptionsParser(mountInfo[mountInfoLength-1]),
 	}
 
-	mount.MountID, err = strconv.Atoi(mountInfo[0])
+	mount.MountId, err = strconv.Atoi(mountInfo[0])
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse mount ID")
 	}
-	mount.ParentID, err = strconv.Atoi(mountInfo[1])
+	mount.ParentId, err = strconv.Atoi(mountInfo[1])
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse parent ID")
 	}
diff --git a/net_softnet.go b/net_softnet.go
index db5debd..2a65cf1 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -25,9 +25,7 @@ import (
 )
 
 // For the proc file format details,
-// See:
-// * Linux 2.6.23 https://elixir.bootlin.com/linux/v2.6.23/source/net/core/dev.c#L2343
-// * Linux 4.17 https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
+// see https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
 // and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810.
 
 // SoftnetStat contains a single row of data from /proc/net/softnet_stat
@@ -40,11 +38,9 @@ type SoftnetStat struct {
 	TimeSqueezed uint32
 }
 
-var softNetProcFile = "net/softnet_stat"
-
 // NetSoftnetStat reads data from /proc/net/softnet_stat.
 func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
-	b, err := util.ReadFileNoStat(fs.proc.Path(softNetProcFile))
+	b, err := util.ReadFileNoStat(fs.proc.Path("net/softnet_stat"))
 	if err != nil {
 		return nil, err
 	}
@@ -58,7 +54,7 @@ func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
 }
 
 func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
-	const minColumns = 9
+	const expectedColumns = 11
 
 	s := bufio.NewScanner(r)
 
@@ -67,8 +63,8 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
 		columns := strings.Fields(s.Text())
 		width := len(columns)
 
-		if width < minColumns {
-			return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns)
+		if width != 11 {
+			return nil, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
 		}
 
 		// We only parse the first three columns at the moment.
diff --git a/proc_cgroup.go b/proc_cgroup.go
new file mode 100644
index 0000000..4abd464
--- /dev/null
+++ b/proc_cgroup.go
@@ -0,0 +1,98 @@
+// Copyright 2020 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"
+	"bytes"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// Cgroup models one line from /proc/[pid]/cgroup. Each Cgroup struct describes the the placement of a PID inside a
+// specific control hierarchy. The kernel has two cgroup APIs, v1 and v2. v1 has one hierarchy per available resource
+// controller, while v2 has one unified hierarchy shared by all controllers. Regardless of v1 or v2, all hierarchies
+// contain all running processes, so the question answerable with a Cgroup struct is 'where is this process in
+// this hierarchy' (where==what path on the specific cgroupfs). By prefixing this path with the mount point of
+// *this specific* hierarchy, you can locate the relevant pseudo-files needed to read/set the data for this PID
+// in this hierarchy
+//
+// Also see http://man7.org/linux/man-pages/man7/cgroups.7.html
+type Cgroup struct {
+	// HierarchyID that can be matched to a named hierarchy using /proc/cgroups. Cgroups V2 only has one
+	// hierarchy, so HierarchyID is always 0. For cgroups v1 this is a unique ID number
+	HierarchyID int
+	// Controllers using this hierarchy of processes. Controllers are also known as subsystems. For
+	// Cgroups V2 this may be empty, as all active controllers use the same hierarchy
+	Controllers []string
+	// Path of this control group, relative to the mount point of the cgroupfs representing this specific
+	// hierarchy
+	Path string
+}
+
+// parseCgroupString parses each line of the /proc/[pid]/cgroup file
+// Line format is hierarchyID:[controller1,controller2]:path
+func parseCgroupString(cgroupStr string) (*Cgroup, error) {
+	var err error
+
+	fields := strings.Split(cgroupStr, ":")
+	if len(fields) < 3 {
+		return nil, fmt.Errorf("at least 3 fields required, found %d fields in cgroup string: %s", len(fields), cgroupStr)
+	}
+
+	cgroup := &Cgroup{
+		Path:        fields[2],
+		Controllers: nil,
+	}
+	cgroup.HierarchyID, err = strconv.Atoi(fields[0])
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse hierarchy ID")
+	}
+	if fields[1] != "" {
+		ssNames := strings.Split(fields[1], ",")
+		cgroup.Controllers = append(cgroup.Controllers, ssNames...)
+	}
+	return cgroup, nil
+}
+
+// parseCgroups reads each line of the /proc/[pid]/cgroup file
+func parseCgroups(data []byte) ([]Cgroup, error) {
+	var cgroups []Cgroup
+	scanner := bufio.NewScanner(bytes.NewReader(data))
+	for scanner.Scan() {
+		mountString := scanner.Text()
+		parsedMounts, err := parseCgroupString(mountString)
+		if err != nil {
+			return nil, err
+		}
+		cgroups = append(cgroups, *parsedMounts)
+	}
+
+	err := scanner.Err()
+	return cgroups, err
+}
+
+// Cgroups reads from /proc/<pid>/cgroups and returns a []*Cgroup struct locating this PID in each process
+// control hierarchy running on this system. On every system (v1 and v2), all hierarchies contain all processes,
+// so the len of the returned struct is equal to the number of active hierarchies on this system
+func (p Proc) Cgroups() ([]Cgroup, error) {
+	data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/cgroup", p.PID))
+	if err != nil {
+		return nil, err
+	}
+	return parseCgroups(data)
+}
diff --git a/proc_fdinfo.go b/proc_fdinfo.go
index 0c9c402..4e7597f 100644
--- a/proc_fdinfo.go
+++ b/proc_fdinfo.go
@@ -16,7 +16,6 @@ package procfs
 import (
 	"bufio"
 	"bytes"
-	"errors"
 	"regexp"
 
 	"github.com/prometheus/procfs/internal/util"
@@ -24,11 +23,10 @@ import (
 
 // 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`)
-	rInotifyParts = regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)(?:\s+mask:([0-9a-f]+))?`)
+	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.
@@ -98,21 +96,15 @@ type InotifyInfo struct {
 
 // InotifyInfo constructor. Only available on kernel 3.8+.
 func parseInotifyInfo(line string) (*InotifyInfo, error) {
-	m := rInotifyParts.FindStringSubmatch(line)
-	if len(m) >= 4 {
-		var mask string
-		if len(m) == 5 {
-			mask = m[4]
-		}
-		i := &InotifyInfo{
-			WD:   m[1],
-			Ino:  m[2],
-			Sdev: m[3],
-			Mask: mask,
-		}
-		return i, nil
+	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 nil, errors.New("invalid inode entry: " + line)
+	return i, nil
 }
 
 // ProcFDInfos represents a list of ProcFDInfo structs.
diff --git a/proc_maps.go b/proc_maps.go
deleted file mode 100644
index 28d5c6e..0000000
--- a/proc_maps.go
+++ /dev/null
@@ -1,208 +0,0 @@
-// 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 (
-	"bufio"
-	"fmt"
-	"os"
-	"strconv"
-	"strings"
-
-	"golang.org/x/sys/unix"
-)
-
-type ProcMapPermissions struct {
-	// mapping has the [R]ead flag set
-	Read bool
-	// mapping has the [W]rite flag set
-	Write bool
-	// mapping has the [X]ecutable flag set
-	Execute bool
-	// mapping has the [S]hared flag set
-	Shared bool
-	// mapping is marked as [P]rivate (copy on write)
-	Private bool
-}
-
-// ProcMap contains the process memory-mappings of the process,
-// read from /proc/[pid]/maps
-type ProcMap struct {
-	// The start address of current mapping.
-	StartAddr uintptr
-	// The end address of the current mapping
-	EndAddr uintptr
-	// The permissions for this mapping
-	Perms *ProcMapPermissions
-	// The current offset into the file/fd (e.g., shared libs)
-	Offset int64
-	// Device owner of this mapping (major:minor) in Mkdev format.
-	Dev uint64
-	// The inode of the device above
-	Inode uint64
-	// The file or psuedofile (or empty==anonymous)
-	Pathname string
-}
-
-// 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("unexpected number of fields")
-	}
-
-	major, err := strconv.ParseUint(toks[0], 16, 0)
-	if err != nil {
-		return 0, err
-	}
-
-	minor, err := strconv.ParseUint(toks[1], 16, 0)
-	if err != nil {
-		return 0, err
-	}
-
-	return unix.Mkdev(uint32(major), uint32(minor)), nil
-}
-
-// parseAddress just converts a hex-string to a uintptr
-func parseAddress(s string) (uintptr, error) {
-	a, err := strconv.ParseUint(s, 16, 0)
-	if err != nil {
-		return 0, err
-	}
-
-	return uintptr(a), nil
-}
-
-// 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("invalid address")
-	}
-
-	saddr, err := parseAddress(toks[0])
-	if err != nil {
-		return 0, 0, err
-	}
-
-	eaddr, err := parseAddress(toks[1])
-	if err != nil {
-		return 0, 0, err
-	}
-
-	return saddr, eaddr, nil
-}
-
-// parsePermissions parses a token and returns any that are set.
-func parsePermissions(s string) (*ProcMapPermissions, error) {
-	if len(s) < 4 {
-		return nil, fmt.Errorf("invalid permissions token")
-	}
-
-	perms := ProcMapPermissions{}
-	for _, ch := range s {
-		switch ch {
-		case 'r':
-			perms.Read = true
-		case 'w':
-			perms.Write = true
-		case 'x':
-			perms.Execute = true
-		case 'p':
-			perms.Private = true
-		case 's':
-			perms.Shared = true
-		}
-	}
-
-	return &perms, nil
-}
-
-// parseProcMap will attempt to parse a single line within a proc/[pid]/maps
-// buffer.
-func parseProcMap(text string) (*ProcMap, error) {
-	fields := strings.Fields(text)
-	if len(fields) < 5 {
-		return nil, fmt.Errorf("truncated procmap entry")
-	}
-
-	saddr, eaddr, err := parseAddresses(fields[0])
-	if err != nil {
-		return nil, err
-	}
-
-	perms, err := parsePermissions(fields[1])
-	if err != nil {
-		return nil, err
-	}
-
-	offset, err := strconv.ParseInt(fields[2], 16, 0)
-	if err != nil {
-		return nil, err
-	}
-
-	device, err := parseDevice(fields[3])
-	if err != nil {
-		return nil, err
-	}
-
-	inode, err := strconv.ParseUint(fields[4], 10, 0)
-	if err != nil {
-		return nil, err
-	}
-
-	pathname := ""
-
-	if len(fields) >= 5 {
-		pathname = strings.Join(fields[5:], " ")
-	}
-
-	return &ProcMap{
-		StartAddr: saddr,
-		EndAddr:   eaddr,
-		Perms:     perms,
-		Offset:    offset,
-		Dev:       device,
-		Inode:     inode,
-		Pathname:  pathname,
-	}, nil
-}
-
-// ProcMaps reads from /proc/[pid]/maps to get the memory-mappings of the
-// process.
-func (p Proc) ProcMaps() ([]*ProcMap, error) {
-	file, err := os.Open(p.path("maps"))
-	if err != nil {
-		return nil, err
-	}
-	defer file.Close()
-
-	maps := []*ProcMap{}
-	scan := bufio.NewScanner(file)
-
-	for scan.Scan() {
-		m, err := parseProcMap(scan.Text())
-		if err != nil {
-			return nil, err
-		}
-
-		maps = append(maps, m)
-	}
-
-	return maps, nil
-}
diff --git a/proc_status.go b/proc_status.go
index c58346d..17b4516 100644
--- a/proc_status.go
+++ b/proc_status.go
@@ -33,37 +33,37 @@ type ProcStatus struct {
 	TGID int
 
 	// Peak virtual memory size.
-	VmPeak uint64 // nolint:golint
+	VmPeak uint64
 	// Virtual memory size.
-	VmSize uint64 // nolint:golint
+	VmSize uint64
 	// Locked memory size.
-	VmLck uint64 // nolint:golint
+	VmLck uint64
 	// Pinned memory size.
-	VmPin uint64 // nolint:golint
+	VmPin uint64
 	// Peak resident set size.
-	VmHWM uint64 // nolint:golint
+	VmHWM uint64
 	// Resident set size (sum of RssAnnon RssFile and RssShmem).
-	VmRSS uint64 // nolint:golint
+	VmRSS uint64
 	// Size of resident anonymous memory.
-	RssAnon uint64 // nolint:golint
+	RssAnon uint64
 	// Size of resident file mappings.
-	RssFile uint64 // nolint:golint
+	RssFile uint64
 	// Size of resident shared memory.
-	RssShmem uint64 // nolint:golint
+	RssShmem uint64
 	// Size of data segments.
-	VmData uint64 // nolint:golint
+	VmData uint64
 	// Size of stack segments.
-	VmStk uint64 // nolint:golint
+	VmStk uint64
 	// Size of text segments.
-	VmExe uint64 // nolint:golint
+	VmExe uint64
 	// Shared library code size.
-	VmLib uint64 // nolint:golint
+	VmLib uint64
 	// Page table entries size.
-	VmPTE uint64 // nolint:golint
+	VmPTE uint64
 	// Size of second-level page tables.
-	VmPMD uint64 // nolint:golint
+	VmPMD uint64
 	// Swapped-out virtual memory size by anonymous private.
-	VmSwap uint64 // nolint:golint
+	VmSwap uint64
 	// Size of hugetlb memory portions
 	HugetlbPages uint64
 
diff --git a/sysfs/class_infiniband.go b/sysfs/class_infiniband.go
index be900fa..9156474 100644
--- a/sysfs/class_infiniband.go
+++ b/sysfs/class_infiniband.go
@@ -97,7 +97,7 @@ func (fs FS) InfiniBandClass() (InfiniBandClass, error) {
 
 	dirs, err := ioutil.ReadDir(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to list InfiniBand devices at %q: %v", path, err)
 	}
 
 	ibc := make(InfiniBandClass, len(dirs))
@@ -251,7 +251,7 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 		name := filepath.Join(path, f.Name())
 		value, err := util.SysReadFile(name)
 		if err != nil {
-			if os.IsNotExist(err) || os.IsPermission(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
+			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
 				continue
 			}
 			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
@@ -334,7 +334,7 @@ func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
 		name := filepath.Join(path, f.Name())
 		value, err := util.SysReadFile(name)
 		if err != nil {
-			if os.IsNotExist(err) || os.IsPermission(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
+			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
 				continue
 			}
 			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
diff --git a/sysfs/net_class.go b/sysfs/net_class.go
index 8f64987..9aa6b57 100644
--- a/sysfs/net_class.go
+++ b/sysfs/net_class.go
@@ -119,7 +119,7 @@ func (nc NetClass) parseNetClassIface(devicePath string) (*NetClassIface, error)
 		name := filepath.Join(devicePath, f.Name())
 		value, err := util.SysReadFile(name)
 		if err != nil {
-			if os.IsNotExist(err) || os.IsPermission(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
+			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
 				continue
 			}
 			return nil, fmt.Errorf("failed to read file %q: %v", name, err)


Test patch:
diff --git a/iscsi/get_test.go b/iscsi/get_test.go
index f21b1ab..a2c24d8 100644
--- a/iscsi/get_test.go
+++ b/iscsi/get_test.go
@@ -160,45 +160,45 @@ func TestGetStats(t *testing.T) {
 				t.Errorf("unexpected iSCSI iops data :\nwant:\n%v\nhave:\n%v", readTests[i].iops, iops)
 			}
 			if stat.Tpgt[0].Luns[0].Backstore == "rd_mcp" {
-				haveRdmcp, err := sysconfigfs.GetRDMCPPath("119", "ramdisk_lio_1G")
+				have_rdmcp, err := sysconfigfs.GetRDMCPPath("119", "ramdisk_lio_1G")
 				if err != nil {
 					t.Errorf("fail rdmcp error %v", err)
 				}
 				// Name ObjectName
-				wantRdmcp := &iscsi.RDMCP{"rd_mcp_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName}
+				want_rdmcp := &iscsi.RDMCP{"rd_mcp_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName}
 
-				if !reflect.DeepEqual(wantRdmcp, haveRdmcp) {
-					t.Errorf("unexpected rdmcp data :\nwant:\n%v\nhave:\n%v", wantRdmcp, haveRdmcp)
+				if !reflect.DeepEqual(want_rdmcp, have_rdmcp) {
+					t.Errorf("unexpected rdmcp data :\nwant:\n%v\nhave:\n%v", want_rdmcp, have_rdmcp)
 				}
 			} else if stat.Tpgt[0].Luns[0].Backstore == "iblock" {
-				haveIblock, err := sysconfigfs.GetIblockUdev("0", "block_lio_rbd1")
+				have_iblock, err := sysconfigfs.GetIblockUdev("0", "block_lio_rbd1")
 				if err != nil {
 					t.Errorf("fail iblock error %v", err)
 				}
 				// Name Bnumber ObjectName Iblock
-				wantIblock := &iscsi.IBLOCK{"iblock_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName, "/dev/rbd1"}
-				if !reflect.DeepEqual(wantIblock, haveIblock) {
-					t.Errorf("unexpected iblock data :\nwant:\n%v\nhave:\n%v", wantIblock, haveIblock)
+				want_iblock := &iscsi.IBLOCK{"iblock_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName, "/dev/rbd1"}
+				if !reflect.DeepEqual(want_iblock, have_iblock) {
+					t.Errorf("unexpected iblock data :\nwant:\n%v\nhave:\n%v", want_iblock, have_iblock)
 				}
 			} else if stat.Tpgt[0].Luns[0].Backstore == "fileio" {
-				haveFileIO, err := sysconfigfs.GetFileioUdev("1", "file_lio_1G")
+				have_fileio, err := sysconfigfs.GetFileioUdev("1", "file_lio_1G")
 				if err != nil {
 					t.Errorf("fail fileio error %v", err)
 				}
 				// Name, Fnumber, ObjectName, Filename
-				wantFileIO := &iscsi.FILEIO{"fileio_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "file_lio_1G", "/home/iscsi/file_back_1G"}
-				if !reflect.DeepEqual(wantFileIO, haveFileIO) {
-					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", wantFileIO, haveFileIO)
+				want_fileio := &iscsi.FILEIO{"fileio_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "file_lio_1G", "/home/iscsi/file_back_1G"}
+				if !reflect.DeepEqual(want_fileio, have_fileio) {
+					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", want_fileio, have_fileio)
 				}
 			} else if stat.Tpgt[0].Luns[0].Backstore == "rbd" {
-				haveRBD, err := sysconfigfs.GetRBDMatch("0", "iscsi-images-demo")
+				have_rbd, err := sysconfigfs.GetRBDMatch("0", "iscsi-images-demo")
 				if err != nil {
 					t.Errorf("fail rbd error %v", err)
 				}
 				// Name, Rnumber, Pool, Image
-				wantRBD := &iscsi.RBD{"rbd_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "iscsi-images", "demo"}
-				if !reflect.DeepEqual(wantRBD, haveRBD) {
-					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", wantRBD, haveRBD)
+				want_rbd := &iscsi.RBD{"rbd_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "iscsi-images", "demo"}
+				if !reflect.DeepEqual(want_rbd, have_rbd) {
+					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", want_rbd, have_rbd)
 				}
 			}
 		}
diff --git a/mountinfo_test.go b/mountinfo_test.go
index de0ecc1..86fe85e 100644
--- a/mountinfo_test.go
+++ b/mountinfo_test.go
@@ -29,8 +29,8 @@ func TestMountInfo(t *testing.T) {
 			s:       "16 21 0:16 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw",
 			invalid: false,
 			mount: &MountInfo{
-				MountID:        16,
-				ParentID:       21,
+				MountId:        16,
+				ParentId:       21,
 				MajorMinorVer:  "0:16",
 				Root:           "/",
 				MountPoint:     "/sys",
@@ -50,8 +50,8 @@ func TestMountInfo(t *testing.T) {
 			name: "Tmpfs mounted at /run",
 			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
 			mount: &MountInfo{
-				MountID:        225,
-				ParentID:       20,
+				MountId:        225,
+				ParentId:       20,
 				MajorMinorVer:  "0:39",
 				Root:           "/",
 				MountPoint:     "/run/user/112",
@@ -67,8 +67,8 @@ func TestMountInfo(t *testing.T) {
 			name: "Tmpfs mounted at /run, but no optional values",
 			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime  - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
 			mount: &MountInfo{
-				MountID:        225,
-				ParentID:       20,
+				MountId:        225,
+				ParentId:       20,
 				MajorMinorVer:  "0:39",
 				Root:           "/",
 				MountPoint:     "/run/user/112",
@@ -84,8 +84,8 @@ func TestMountInfo(t *testing.T) {
 			name: "Tmpfs mounted at /run, with multiple optional values",
 			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
 			mount: &MountInfo{
-				MountID:        225,
-				ParentID:       20,
+				MountId:        225,
+				ParentId:       20,
 				MajorMinorVer:  "0:39",
 				Root:           "/",
 				MountPoint:     "/run/user/112",
@@ -101,8 +101,8 @@ func TestMountInfo(t *testing.T) {
 			name: "Tmpfs mounted at /run, with a mixture of valid and invalid optional values",
 			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 foo:bar - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
 			mount: &MountInfo{
-				MountID:        225,
-				ParentID:       20,
+				MountId:        225,
+				ParentId:       20,
 				MajorMinorVer:  "0:39",
 				Root:           "/",
 				MountPoint:     "/run/user/112",
@@ -118,8 +118,8 @@ func TestMountInfo(t *testing.T) {
 			name: "CIFS mounted at /with/a-hyphen",
 			s:    "454 29 0:87 / /with/a-hyphen rw,relatime shared:255 - cifs //remote-storage/Path rw,vers=3.1.1,cache=strict,username=user,uid=1000,forceuid,gid=0,noforcegid,addr=127.0.0.1,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,echo_interval=60,actimeo=1",
 			mount: &MountInfo{
-				MountID:        454,
-				ParentID:       29,
+				MountId:        454,
+				ParentId:       29,
 				MajorMinorVer:  "0:87",
 				Root:           "/",
 				MountPoint:     "/with/a-hyphen",
diff --git a/net_softnet_test.go b/net_softnet_test.go
index f25e09f..5a52131 100644
--- a/net_softnet_test.go
+++ b/net_softnet_test.go
@@ -29,11 +29,7 @@ func TestNetSoftnet(t *testing.T) {
 		Processed:    0x00015c73,
 		Dropped:      0x00020e76,
 		TimeSqueezed: 0xf0000769,
-	},
-		{
-			Processed:    0x01663fb2,
-			TimeSqueezed: 0x0109a4,
-		}}
+	}}
 
 	got, err := fs.NetSoftnetStat()
 	if err != nil {
@@ -44,16 +40,3 @@ func TestNetSoftnet(t *testing.T) {
 		t.Fatalf("unexpected softnet stats(-want +got):\n%s", diff)
 	}
 }
-
-func TestBadSoftnet(t *testing.T) {
-	softNetProcFile = "net/softnet_stat.broken"
-	fs, err := NewFS(procTestFixtures)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	_, err = fs.NetSoftnetStat()
-	if err == nil {
-		t.Fatal("expected error, got nil")
-	}
-}
diff --git a/proc_cgroup_test.go b/proc_cgroup_test.go
new file mode 100644
index 0000000..f530edb
--- /dev/null
+++ b/proc_cgroup_test.go
@@ -0,0 +1,93 @@
+// Copyright 2020 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 TestParseCgroupString(t *testing.T) {
+	tests := []struct {
+		name      string
+		s         string
+		shouldErr bool
+		cgroup    *Cgroup
+	}{
+		{
+			name:      "cgroups-v1 simple line",
+			s:         "10:rdma:/",
+			shouldErr: false,
+			cgroup: &Cgroup{
+				HierarchyID: 10,
+				Controllers: []string{"rdma"},
+				Path:        "/",
+			},
+		},
+		{
+			name:      "cgroups-v1 multi-hier line",
+			s:         "3:cpu,cpuacct:/user.slice/user-1000.slice/session-10.scope",
+			shouldErr: false,
+			cgroup: &Cgroup{
+				HierarchyID: 3,
+				Controllers: []string{"cpu", "cpuacct"},
+				Path:        "/user.slice/user-1000.slice/session-10.scope",
+			},
+		},
+		{
+			name:      "cgroup-v2 line",
+			s:         "0::/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service",
+			shouldErr: false,
+			cgroup: &Cgroup{
+				HierarchyID: 0,
+				Controllers: nil,
+				Path:        "/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service",
+			},
+		},
+		{
+			name:      "extra fields (such as those added by later kernel versions)",
+			s:         "0::/:foobar",
+			shouldErr: false,
+			cgroup: &Cgroup{
+				HierarchyID: 0,
+				Controllers: nil,
+				Path:        "/",
+			},
+		},
+		{
+			name:      "bad hierarchy ID field",
+			s:         "a:cpu:/",
+			shouldErr: true,
+			cgroup:    nil,
+		},
+	}
+
+	for i, test := range tests {
+		t.Logf("[%02d] test %q", i, test.name)
+
+		cgroup, err := parseCgroupString(test.s)
+
+		if test.shouldErr && err == nil {
+			t.Errorf("%s: expected an error, but none occurred", test.name)
+		}
+		if !test.shouldErr && err != nil {
+			t.Errorf("%s: unexpected error: %v", test.name, err)
+		}
+
+		if want, have := test.cgroup, cgroup; !reflect.DeepEqual(want, have) {
+			t.Errorf("cgroup:\nwant:\n%+v\nhave:\n%+v", want, have)
+		}
+	}
+
+}
diff --git a/proc_maps_test.go b/proc_maps_test.go
deleted file mode 100644
index 9bc8e95..0000000
--- a/proc_maps_test.go
+++ /dev/null
@@ -1,185 +0,0 @@
-// 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 (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"golang.org/x/sys/unix"
-)
-
-func TestProcMaps(t *testing.T) {
-	tsts32 := []*ProcMap{
-		{
-			StartAddr: 0x08048000,
-			EndAddr:   0x08089000,
-			Perms:     &ProcMapPermissions{true, false, true, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0x03, 0x01),
-			Inode:     104219,
-			Pathname:  "/bin/tcsh",
-		},
-		{
-			StartAddr: 0x08089000,
-			EndAddr:   0x0808c000,
-			Perms:     &ProcMapPermissions{true, true, false, false, true},
-			Offset:    266240,
-			Dev:       unix.Mkdev(0x03, 0x01),
-			Inode:     104219,
-			Pathname:  "/bin/tcsh",
-		},
-		{
-			StartAddr: 0x0808c000,
-			EndAddr:   0x08146000,
-			Perms:     &ProcMapPermissions{true, true, true, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0x00, 0x00),
-			Inode:     0,
-			Pathname:  "",
-		},
-		{
-			StartAddr: 0x40000000,
-			EndAddr:   0x40015000,
-			Perms:     &ProcMapPermissions{true, false, true, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0x03, 0x01),
-			Inode:     61874,
-			Pathname:  "/lib/ld-2.3.2.so",
-		},
-	}
-
-	tsts64 := []*ProcMap{
-		{
-			StartAddr: 0x55680ae1e000,
-			EndAddr:   0x55680ae20000,
-			Perms:     &ProcMapPermissions{true, false, false, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0xfd, 0x01),
-			Inode:     47316994,
-			Pathname:  "/bin/cat",
-		},
-		{
-			StartAddr: 0x55680ae29000,
-			EndAddr:   0x55680ae2a000,
-			Perms:     &ProcMapPermissions{true, true, true, true, false},
-			Offset:    40960,
-			Dev:       unix.Mkdev(0xfd, 0x01),
-			Inode:     47316994,
-			Pathname:  "/bin/cat",
-		},
-		{
-			StartAddr: 0x55680bed6000,
-			EndAddr:   0x55680bef7000,
-			Perms:     &ProcMapPermissions{true, true, false, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0, 0),
-			Inode:     0,
-			Pathname:  "[heap]",
-		},
-		{
-			StartAddr: 0x7fdf964fc000,
-			EndAddr:   0x7fdf973f2000,
-			Perms:     &ProcMapPermissions{true, false, false, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0xfd, 0x01),
-			Inode:     17432624,
-			Pathname:  "/usr/lib/locale/locale-archive",
-		},
-		{
-			StartAddr: 0x7fdf973f2000,
-			EndAddr:   0x7fdf97417000,
-			Perms:     &ProcMapPermissions{true, false, false, false, true},
-			Offset:    0,
-			Dev:       unix.Mkdev(0xfd, 0x01),
-			Inode:     60571062,
-			Pathname:  "/lib/x86_64-linux-gnu/libc-2.29.so",
-		},
-		{
-			StartAddr: 0x7ffe9215c000,
-			EndAddr:   0x7ffe9217f000,
-			Perms:     &ProcMapPermissions{true, true, false, false, true},
-			Offset:    0,
-			Dev:       0,
-			Inode:     0,
-			Pathname:  "[stack]",
-		},
-		{
-			StartAddr: 0x7ffe921da000,
-			EndAddr:   0x7ffe921dd000,
-			Perms:     &ProcMapPermissions{true, false, false, false, true},
-			Offset:    0,
-			Dev:       0,
-			Inode:     0,
-			Pathname:  "[vvar]",
-		},
-		{
-			StartAddr: 0x7ffe921dd000,
-			EndAddr:   0x7ffe921de000,
-			Perms:     &ProcMapPermissions{true, false, true, false, true},
-			Offset:    0,
-			Dev:       0,
-			Inode:     0,
-			Pathname:  "[vdso]",
-		},
-		{
-			StartAddr: 0xffffffffff600000,
-			EndAddr:   0xffffffffff601000,
-			Perms:     &ProcMapPermissions{false, false, true, false, true},
-			Offset:    0,
-			Dev:       0,
-			Inode:     0,
-			Pathname:  "[vsyscall]",
-		},
-	}
-
-	var (
-		tsts []*ProcMap
-		tpid int
-	)
-
-	if (32 << uintptr(^uintptr(0)>>63)) == 64 {
-		// 64b test pid
-		tpid = 26232
-		tsts = tsts64
-	} else {
-		// 32b test pid
-		tpid = 26234
-		tsts = tsts32
-	}
-
-	p, err := getProcFixtures(t).Proc(tpid)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	maps, err := p.ProcMaps()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if want, have := len(maps), len(tsts); want > have {
-		t.Errorf("want at least %d parsed proc/map entries, have %d", want, have)
-	}
-
-	for idx, tst := range tsts {
-		want, got := tst, maps[idx]
-		if diff := cmp.Diff(want, got); diff != "" {
-			t.Fatalf("unexpected proc/map entry (-want +got):\n%s", diff)
-		}
-	}
-
-}
diff --git a/sysfs/net_class_test.go b/sysfs/net_class_test.go
index 41963db..39a7c27 100644
--- a/sysfs/net_class_test.go
+++ b/sysfs/net_class_test.go
@@ -65,7 +65,7 @@ func TestNetClass(t *testing.T) {
 		linkMode         int64 = 1
 		mtu              int64 = 1500
 		nameAssignType   int64 = 2
-		netDevGroup      int64
+		netDevGroup      int64 = 0
 		speed            int64 = 1000
 		txQueueLen       int64 = 1000
 		netType          int64 = 1