Repository: prometheus/procfs
Pull request #253 (2020-01-07 04:41:46)

/proc/[pid]/maps parser
Hello Prometheus Team,

My name is Timothy and I am new at the Git hub so please bare with me as to also english is not the first of languages of which I have learned. I am a very big fan of your time series! It is probably my favorite of all the series, so I thank you for that project first.

We are a small but simple team of developers here, we have mostly all quit smoking, but all writing tools in which debug memories in the linux system. Unfortunately this is in Golang and we are using procfs, maybe you would like support for the things in which we have added?

Thank you so much for your continued support of the time series! Please call or tiktok me if you need any updates. I am sorry for any problems! I made sure all additions met requirements. Thank you again!

timothy-boners: Thank you very much for the comments! I will update the next morning!

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index a21ccad..cb88f40 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -289,6 +289,19 @@ 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -317,6 +330,17 @@ 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1527,11 +1551,6 @@ 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
@@ -1833,18 +1852,18 @@ Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp
 Lines: 4
-  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
-   0: 0A000005:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
-   1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
-   2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0
+  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
+   0: 00000000:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
+   1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
+   2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000     0        0 2740 1 ffff88003d3af3c0 100 0 0 10 0                      
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp6
 Lines: 3
   sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops
  1315: 00000000000000000000000000000000:14EB 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000   981        0 21040 2 0000000013726323 0
- 6073: 000080FE00000000FFADE15609667CFE:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000  1000        0 11337031 2 00000000b9256fdd 0
-Mode: 644
+ 6073: 00000000000000000000000000000000:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000  1000        0 11337031 2 00000000b9256fdd 0
+Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/udp_broken
 Lines: 2
@@ -1957,12 +1976,6 @@ procs_blocked 1
 softirq 5057579 250191 1481983 1647 211099 186066 0 1783454 622196 12499 508444
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/swaps
-Lines: 2
-Filename				Type		Size	Used	Priority
-/dev/dm-2                               partition	131068	176	-2
-Mode: 444
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/symlinktargets
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/go.mod b/go.mod
index 0e04e5d..ded4825 100644
--- a/go.mod
+++ b/go.mod
@@ -5,4 +5,5 @@ 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 33b824b..54b5f33 100644
--- a/go.sum
+++ b/go.sum
@@ -2,3 +2,5 @@ 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/loadavg.go b/loadavg.go
deleted file mode 100644
index 00bbe14..0000000
--- a/loadavg.go
+++ /dev/null
@@ -1,62 +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.
-
-package procfs
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
-)
-
-// LoadAvg represents an entry in /proc/loadavg
-type LoadAvg struct {
-	Load1  float64
-	Load5  float64
-	Load15 float64
-}
-
-// LoadAvg returns loadavg from /proc.
-func (fs FS) LoadAvg() (*LoadAvg, error) {
-	path := fs.proc.Path("loadavg")
-
-	data, err := util.ReadFileNoStat(path)
-	if err != nil {
-		return nil, err
-	}
-	return parseLoad(data)
-}
-
-// Parse /proc loadavg and return 1m, 5m and 15m.
-func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
-	loads := make([]float64, 3)
-	parts := strings.Fields(string(loadavgBytes))
-	if len(parts) < 3 {
-		return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %s", string(loadavgBytes))
-	}
-
-	var err error
-	for i, load := range parts[0:3] {
-		loads[i], err = strconv.ParseFloat(load, 64)
-		if err != nil {
-			return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
-		}
-	}
-	return &LoadAvg{
-		Load1:  loads[0],
-		Load5:  loads[1],
-		Load15: loads[2],
-	}, nil
-}
diff --git a/net_conntrackstat.go b/net_conntrackstat.go
deleted file mode 100644
index 1e27c83..0000000
--- a/net_conntrackstat.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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"
-	"io"
-	"strconv"
-	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
-)
-
-// A ConntrackStatEntry represents one line from net/stat/nf_conntrack
-// and contains netfilter conntrack statistics at one CPU core
-type ConntrackStatEntry struct {
-	Entries       uint64
-	Found         uint64
-	Invalid       uint64
-	Ignore        uint64
-	Insert        uint64
-	InsertFailed  uint64
-	Drop          uint64
-	EarlyDrop     uint64
-	SearchRestart uint64
-}
-
-// Retrieves netfilter's conntrack statistics, split by CPU cores
-func (fs FS) ConntrackStat() ([]ConntrackStatEntry, error) {
-	return readConntrackStat(fs.proc.Path("net", "stat", "nf_conntrack"))
-}
-
-// Parses a slice of ConntrackStatEntries from the given filepath
-func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
-	// This file is small and can be read with one syscall.
-	b, err := util.ReadFileNoStat(path)
-	if err != nil {
-		// Do not wrap this error so the caller can detect os.IsNotExist and
-		// similar conditions.
-		return nil, err
-	}
-
-	stat, err := parseConntrackStat(bytes.NewReader(b))
-	if err != nil {
-		return nil, fmt.Errorf("failed to read conntrack stats from %q: %v", path, err)
-	}
-
-	return stat, nil
-}
-
-// Reads the contents of a conntrack statistics file and parses a slice of ConntrackStatEntries
-func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
-	var entries []ConntrackStatEntry
-
-	scanner := bufio.NewScanner(r)
-	scanner.Scan()
-	for scanner.Scan() {
-		fields := strings.Fields(scanner.Text())
-		conntrackEntry, err := parseConntrackStatEntry(fields)
-		if err != nil {
-			return nil, err
-		}
-		entries = append(entries, *conntrackEntry)
-	}
-
-	return entries, nil
-}
-
-// Parses a ConntrackStatEntry from given array of fields
-func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
-	if len(fields) != 17 {
-		return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
-	}
-	entry := &ConntrackStatEntry{}
-
-	entries, err := parseConntrackStatField(fields[0])
-	if err != nil {
-		return nil, err
-	}
-	entry.Entries = entries
-
-	found, err := parseConntrackStatField(fields[2])
-	if err != nil {
-		return nil, err
-	}
-	entry.Found = found
-
-	invalid, err := parseConntrackStatField(fields[4])
-	if err != nil {
-		return nil, err
-	}
-	entry.Invalid = invalid
-
-	ignore, err := parseConntrackStatField(fields[5])
-	if err != nil {
-		return nil, err
-	}
-	entry.Ignore = ignore
-
-	insert, err := parseConntrackStatField(fields[8])
-	if err != nil {
-		return nil, err
-	}
-	entry.Insert = insert
-
-	insertFailed, err := parseConntrackStatField(fields[9])
-	if err != nil {
-		return nil, err
-	}
-	entry.InsertFailed = insertFailed
-
-	drop, err := parseConntrackStatField(fields[10])
-	if err != nil {
-		return nil, err
-	}
-	entry.Drop = drop
-
-	earlyDrop, err := parseConntrackStatField(fields[11])
-	if err != nil {
-		return nil, err
-	}
-	entry.EarlyDrop = earlyDrop
-
-	searchRestart, err := parseConntrackStatField(fields[16])
-	if err != nil {
-		return nil, err
-	}
-	entry.SearchRestart = searchRestart
-
-	return entry, nil
-}
-
-// Parses a uint64 from given hex in string
-func parseConntrackStatField(field string) (uint64, error) {
-	val, err := strconv.ParseUint(field, 16, 64)
-	if err != nil {
-		return 0, fmt.Errorf("couldn't parse \"%s\" field: %s", field, err)
-	}
-	return val, err
-}
diff --git a/net_softnet.go b/net_softnet.go
index 2a65cf1..6fcad20 100644
--- a/net_softnet.go
+++ b/net_softnet.go
@@ -14,85 +14,78 @@
 package procfs
 
 import (
-	"bufio"
-	"bytes"
 	"fmt"
-	"io"
+	"io/ioutil"
 	"strconv"
 	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
 )
 
 // For the proc file format details,
 // 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
-type SoftnetStat struct {
+// SoftnetEntry contains a single row of data from /proc/net/softnet_stat
+type SoftnetEntry struct {
 	// Number of processed packets
-	Processed uint32
+	Processed uint
 	// Number of dropped packets
-	Dropped uint32
+	Dropped uint
 	// Number of times processing packets ran out of quota
-	TimeSqueezed uint32
+	TimeSqueezed uint
 }
 
-// NetSoftnetStat reads data from /proc/net/softnet_stat.
-func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
-	b, err := util.ReadFileNoStat(fs.proc.Path("net/softnet_stat"))
+// GatherSoftnetStats reads /proc/net/softnet_stat, parse the relevant columns,
+// and then return a slice of SoftnetEntry's.
+func (fs FS) GatherSoftnetStats() ([]SoftnetEntry, error) {
+	data, err := ioutil.ReadFile(fs.proc.Path("net/softnet_stat"))
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error reading softnet %s: %s", fs.proc.Path("net/softnet_stat"), err)
 	}
 
-	entries, err := parseSoftnet(bytes.NewReader(b))
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse /proc/net/softnet_stat: %v", err)
-	}
-
-	return entries, nil
+	return parseSoftnetEntries(data)
 }
 
-func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
-	const expectedColumns = 11
-
-	s := bufio.NewScanner(r)
-
-	var stats []SoftnetStat
-	for s.Scan() {
-		columns := strings.Fields(s.Text())
+func parseSoftnetEntries(data []byte) ([]SoftnetEntry, error) {
+	lines := strings.Split(string(data), "\n")
+	entries := make([]SoftnetEntry, 0)
+	var err error
+	const (
+		expectedColumns = 11
+	)
+	for _, line := range lines {
+		columns := strings.Fields(line)
 		width := len(columns)
-
-		if width != 11 {
-			return nil, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
+		if width == 0 {
+			continue
 		}
-
-		// We only parse the first three columns at the moment.
-		us, err := parseHexUint32s(columns[0:3])
-		if err != nil {
-			return nil, err
+		if width != expectedColumns {
+			return []SoftnetEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
 		}
-
-		stats = append(stats, SoftnetStat{
-			Processed:    us[0],
-			Dropped:      us[1],
-			TimeSqueezed: us[2],
-		})
+		var entry SoftnetEntry
+		if entry, err = parseSoftnetEntry(columns); err != nil {
+			return []SoftnetEntry{}, err
+		}
+		entries = append(entries, entry)
 	}
 
-	return stats, nil
+	return entries, nil
 }
 
-func parseHexUint32s(ss []string) ([]uint32, error) {
-	us := make([]uint32, 0, len(ss))
-	for _, s := range ss {
-		u, err := strconv.ParseUint(s, 16, 32)
-		if err != nil {
-			return nil, err
-		}
-
-		us = append(us, uint32(u))
+func parseSoftnetEntry(columns []string) (SoftnetEntry, error) {
+	var err error
+	var processed, dropped, timeSqueezed uint64
+	if processed, err = strconv.ParseUint(columns[0], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 0: %s", err)
 	}
-
-	return us, nil
+	if dropped, err = strconv.ParseUint(columns[1], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 1: %s", err)
+	}
+	if timeSqueezed, err = strconv.ParseUint(columns[2], 16, 32); err != nil {
+		return SoftnetEntry{}, fmt.Errorf("Unable to parse column 2: %s", err)
+	}
+	return SoftnetEntry{
+		Processed:    uint(processed),
+		Dropped:      uint(dropped),
+		TimeSqueezed: uint(timeSqueezed),
+	}, nil
 }
diff --git a/net_udp.go b/net_udp.go
index d017e3f..1799119 100644
--- a/net_udp.go
+++ b/net_udp.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Prometheus Authors
+// 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
@@ -15,10 +15,8 @@ package procfs
 
 import (
 	"bufio"
-	"encoding/hex"
 	"fmt"
 	"io"
-	"net"
 	"os"
 	"strconv"
 	"strings"
@@ -56,9 +54,9 @@ type (
 	// For the proc file format details, see https://linux.die.net/man/5/proc.
 	netUDPLine struct {
 		Sl        uint64
-		LocalAddr net.IP
+		LocalAddr uint64
 		LocalPort uint64
-		RemAddr   net.IP
+		RemAddr   uint64
 		RemPort   uint64
 		St        uint64
 		TxQueue   uint64
@@ -174,7 +172,7 @@ func parseNetUDPLine(fields []string) (*netUDPLine, error) {
 		return nil, fmt.Errorf(
 			"cannot parse local_address field in udp socket line: %s", fields[1])
 	}
-	if line.LocalAddr, err = hex.DecodeString(l[0]); err != nil {
+	if line.LocalAddr, err = strconv.ParseUint(l[0], 16, 64); err != nil {
 		return nil, fmt.Errorf(
 			"cannot parse local_address value in udp socket line: %s", err)
 	}
@@ -189,7 +187,7 @@ func parseNetUDPLine(fields []string) (*netUDPLine, error) {
 		return nil, fmt.Errorf(
 			"cannot parse rem_address field in udp socket line: %s", fields[1])
 	}
-	if line.RemAddr, err = hex.DecodeString(r[0]); err != nil {
+	if line.RemAddr, err = strconv.ParseUint(r[0], 16, 64); err != nil {
 		return nil, fmt.Errorf(
 			"cannot parse rem_address value in udp socket line: %s", err)
 	}
diff --git a/proc_maps.go b/proc_maps.go
new file mode 100644
index 0000000..28d5c6e
--- /dev/null
+++ b/proc_maps.go
@@ -0,0 +1,208 @@
+// 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/swaps.go b/swaps.go
deleted file mode 100644
index 15edc22..0000000
--- a/swaps.go
+++ /dev/null
@@ -1,89 +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.
-
-package procfs
-
-import (
-	"bufio"
-	"bytes"
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/prometheus/procfs/internal/util"
-)
-
-// Swap represents an entry in /proc/swaps.
-type Swap struct {
-	Filename string
-	Type     string
-	Size     int
-	Used     int
-	Priority int
-}
-
-// Swaps returns a slice of all configured swap devices on the system.
-func (fs FS) Swaps() ([]*Swap, error) {
-	data, err := util.ReadFileNoStat(fs.proc.Path("swaps"))
-	if err != nil {
-		return nil, err
-	}
-	return parseSwaps(data)
-}
-
-func parseSwaps(info []byte) ([]*Swap, error) {
-	swaps := []*Swap{}
-	scanner := bufio.NewScanner(bytes.NewReader(info))
-	scanner.Scan() // ignore header line
-	for scanner.Scan() {
-		swapString := scanner.Text()
-		parsedSwap, err := parseSwapString(swapString)
-		if err != nil {
-			return nil, err
-		}
-		swaps = append(swaps, parsedSwap)
-	}
-
-	err := scanner.Err()
-	return swaps, err
-}
-
-func parseSwapString(swapString string) (*Swap, error) {
-	var err error
-
-	swapFields := strings.Fields(swapString)
-	swapLength := len(swapFields)
-	if swapLength < 5 {
-		return nil, fmt.Errorf("too few fields in swap string: %s", swapString)
-	}
-
-	swap := &Swap{
-		Filename: swapFields[0],
-		Type:     swapFields[1],
-	}
-
-	swap.Size, err = strconv.Atoi(swapFields[2])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap size: %s", swapFields[2])
-	}
-	swap.Used, err = strconv.Atoi(swapFields[3])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap used: %s", swapFields[3])
-	}
-	swap.Priority, err = strconv.Atoi(swapFields[4])
-	if err != nil {
-		return nil, fmt.Errorf("invalid swap priority: %s", swapFields[4])
-	}
-
-	return swap, nil
-}
diff --git a/xfs/parse.go b/xfs/parse.go
index ba3afea..f3f30d7 100644
--- a/xfs/parse.go
+++ b/xfs/parse.go
@@ -381,15 +381,11 @@ func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
 }
 
 func quotaManagerStats(us []uint32) (QuotaManagerStats, error) {
-	// The "Unused" attribute first appears in Linux 4.20
-	// As a result either 8 or 9 elements may appear in this slice depending on
-	// the kernel version.
-	l := len(us)
-	if l != 8 && l != 9 {
-		return QuotaManagerStats{}, fmt.Errorf("incorrect number of values for XFS quota stats: %d", l)
+	if l := len(us); l < 8 {
+		return QuotaManagerStats{}, fmt.Errorf("insufficient number of values for XFS quota stats expecting at least 8, got: %d", l)
 	}
 
-	s := QuotaManagerStats{
+	return QuotaManagerStats{
 		Reclaims:      us[0],
 		ReclaimMisses: us[1],
 		DquoteDups:    us[2],
@@ -398,13 +394,7 @@ func quotaManagerStats(us []uint32) (QuotaManagerStats, error) {
 		Wants:         us[5],
 		ShakeReclaims: us[6],
 		InactReclaims: us[7],
-	}
-
-	if l > 8 {
-		s.Unused = us[8]
-	}
-
-	return s, nil
+	}, nil
 }
 
 func debugStats(us []uint32) (DebugStats, error) {
diff --git a/xfs/xfs.go b/xfs/xfs.go
index cc8b36b..018a0b8 100644
--- a/xfs/xfs.go
+++ b/xfs/xfs.go
@@ -202,7 +202,6 @@ type QuotaManagerStats struct {
 	Wants         uint32
 	ShakeReclaims uint32
 	InactReclaims uint32
-	Unused        uint32
 }
 
 // XstratStats contains statistics regarding bytes processed by the XFS daemon.


Test patch:
diff --git a/loadavg_test.go b/loadavg_test.go
deleted file mode 100644
index 924ba0c..0000000
--- a/loadavg_test.go
+++ /dev/null
@@ -1,93 +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.
-
-package procfs
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestLoadAvg(t *testing.T) {
-	fs, err := NewFS(procTestFixtures)
-	if err != nil {
-		t.Fatalf("failed to open procfs: %v", err)
-	}
-
-	loadavg, err := fs.LoadAvg()
-	if err != nil {
-		t.Fatalf("failed to get loadavg: %v", err)
-	}
-
-	if diff := cmp.Diff(0.02, loadavg.Load1); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per a minute:\n%s", diff)
-	}
-	if diff := cmp.Diff(0.04, loadavg.Load5); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per five minutes:\n%s", diff)
-	}
-	if diff := cmp.Diff(0.05, loadavg.Load15); diff != "" {
-		t.Fatalf("unexpected LoadAvg Per fifteen minutes:\n%s", diff)
-	}
-}
-
-func Test_parseLoad(t *testing.T) {
-	tests := []struct {
-		name    string
-		s       string
-		ok      bool
-		loadavg *LoadAvg
-	}{
-		{
-			name: "empty",
-			ok:   false,
-		},
-		{
-			name: "not enough fields",
-			s:    `0.00 0.03`,
-			ok:   false,
-		},
-		{
-			name: "invalid line",
-			s:    `malformed line`,
-			ok:   false,
-		},
-		{
-			name:    "valid line",
-			s:       `0.00 0.03 0.05 1/502 33634`,
-			ok:      true,
-			loadavg: &LoadAvg{Load1: 0, Load5: 0.03, Load15: 0.05},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			loadavg, err := parseLoad([]byte(tt.s))
-			if err != nil {
-				if tt.ok {
-					t.Fatalf("failed to parse loadavg: %v", err)
-				}
-
-				t.Logf("OK error: %v", err)
-				return
-			}
-			if !tt.ok {
-				t.Fatal("expected an error, but none occurred")
-			}
-
-			if diff := cmp.Diff(tt.loadavg, loadavg); diff != "" {
-				t.Errorf("unexpected loadavg(-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/net_conntrackstat_test.go b/net_conntrackstat_test.go
deleted file mode 100644
index 18fc066..0000000
--- a/net_conntrackstat_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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 (
-	"bytes"
-	"reflect"
-	"testing"
-)
-
-func TestParseConntrackStat(t *testing.T) {
-	var nfConntrackStat = []byte(`entries  searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error  expect_new expect_create expect_delete search_restart
-00000021  00000000 00000000 00000000 00000003 0000588a 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
-00000021  00000000 00000000 00000000 00000002 000056a4 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000002
-00000021  00000000 00000000 00000000 00000001 000058d4 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000001
-00000021  00000000 00000000 00000000 0000002f 00005688 00000000 00000000 00000000 00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000004
-`)
-	r := bytes.NewReader(nfConntrackStat)
-
-	have, err := parseConntrackStat(r)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	want := []ConntrackStatEntry{
-		ConntrackStatEntry{
-			Entries:       33,
-			Found:         0,
-			Invalid:       3,
-			Ignore:        22666,
-			Insert:        0,
-			InsertFailed:  0,
-			Drop:          0,
-			EarlyDrop:     0,
-			SearchRestart: 0,
-		},
-		ConntrackStatEntry{
-			Entries:       33,
-			Found:         0,
-			Invalid:       2,
-			Ignore:        22180,
-			Insert:        0,
-			InsertFailed:  0,
-			Drop:          0,
-			EarlyDrop:     0,
-			SearchRestart: 2,
-		},
-		ConntrackStatEntry{
-			Entries:       33,
-			Found:         0,
-			Invalid:       1,
-			Ignore:        22740,
-			Insert:        0,
-			InsertFailed:  0,
-			Drop:          0,
-			EarlyDrop:     0,
-			SearchRestart: 1,
-		},
-		ConntrackStatEntry{
-			Entries:       33,
-			Found:         0,
-			Invalid:       47,
-			Ignore:        22152,
-			Insert:        0,
-			InsertFailed:  0,
-			Drop:          0,
-			EarlyDrop:     0,
-			SearchRestart: 4,
-		},
-	}
-	if !reflect.DeepEqual(want, have) {
-		t.Errorf("want %v, have %v", want, have)
-	}
-}
diff --git a/net_softnet_test.go b/net_softnet_test.go
index 5a52131..a2658a3 100644
--- a/net_softnet_test.go
+++ b/net_softnet_test.go
@@ -15,28 +15,28 @@ package procfs
 
 import (
 	"testing"
-
-	"github.com/google/go-cmp/cmp"
 )
 
-func TestNetSoftnet(t *testing.T) {
+func TestSoftnet(t *testing.T) {
 	fs, err := NewFS(procTestFixtures)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	want := []SoftnetStat{{
-		Processed:    0x00015c73,
-		Dropped:      0x00020e76,
-		TimeSqueezed: 0xf0000769,
-	}}
-
-	got, err := fs.NetSoftnetStat()
+	entries, err := fs.GatherSoftnetStats()
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if diff := cmp.Diff(want, got); diff != "" {
-		t.Fatalf("unexpected softnet stats(-want +got):\n%s", diff)
+	if want, got := uint(0x00015c73), entries[0].Processed; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
+	}
+
+	if want, got := uint(0x00020e76), entries[0].Dropped; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
+	}
+
+	if want, got := uint(0xF0000769), entries[0].TimeSqueezed; want != got {
+		t.Errorf("want %08x, got %08x", want, got)
 	}
 }
diff --git a/net_udp_test.go b/net_udp_test.go
index 363fdb2..563530c 100644
--- a/net_udp_test.go
+++ b/net_udp_test.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Prometheus Authors
+// 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
@@ -14,7 +14,6 @@
 package procfs
 
 import (
-	"net"
 	"reflect"
 	"testing"
 )
@@ -31,9 +30,9 @@ func Test_parseNetUDPLine(t *testing.T) {
 			fields: []string{"11:", "00000000:0000", "00000000:0000", "0A", "00000017:0000002A", "0:0", "0", "1000"},
 			want: &netUDPLine{
 				Sl:        11,
-				LocalAddr: net.IP{0, 0, 0, 0},
+				LocalAddr: 0,
 				LocalPort: 0,
-				RemAddr:   net.IP{0, 0, 0, 0},
+				RemAddr:   0,
 				RemPort:   0,
 				St:        10,
 				TxQueue:   23,
@@ -100,8 +99,19 @@ func Test_parseNetUDPLine(t *testing.T) {
 			if tt.want == nil && got != nil {
 				t.Errorf("parseNetUDPLine() = %v, want %v", got, tt.want)
 			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want)
+			if got != nil {
+				if (got.Sl != tt.want.Sl) ||
+					(got.LocalAddr != tt.want.LocalAddr) ||
+					(got.LocalPort != tt.want.LocalPort) ||
+					(got.RemAddr != tt.want.RemAddr) ||
+					(got.RemPort != tt.want.RemPort) ||
+					(got.St != tt.want.St) ||
+					(got.TxQueue != tt.want.TxQueue) ||
+					(got.RxQueue != tt.want.RxQueue) ||
+					(got.UID != tt.want.UID) {
+
+					t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want)
+				}
 			}
 		})
 	}
@@ -119,37 +129,13 @@ func Test_newNetUDP(t *testing.T) {
 			file: "fixtures/proc/net/udp",
 			want: []*netUDPLine{
 				&netUDPLine{
-					Sl:        0,
-					LocalAddr: net.IP{10, 0, 0, 5},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   0,
-					RxQueue:   1,
-					UID:       0,
+					Sl: 0, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 0, RxQueue: 1, UID: 0,
 				},
 				&netUDPLine{
-					Sl:        1,
-					LocalAddr: net.IP{0, 0, 0, 0},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   1,
-					RxQueue:   0,
-					UID:       0,
+					Sl: 1, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 0, UID: 0,
 				},
 				&netUDPLine{
-					Sl:        2,
-					LocalAddr: net.IP{0, 0, 0, 0},
-					LocalPort: 22,
-					RemAddr:   net.IP{0, 0, 0, 0},
-					RemPort:   0,
-					St:        10,
-					TxQueue:   1,
-					RxQueue:   1,
-					UID:       0,
+					Sl: 2, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 1, UID: 0,
 				},
 			},
 			wantErr: false,
@@ -159,26 +145,10 @@ func Test_newNetUDP(t *testing.T) {
 			file: "fixtures/proc/net/udp6",
 			want: []*netUDPLine{
 				&netUDPLine{
-					Sl:        1315,
-					LocalAddr: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					LocalPort: 5355,
-					RemAddr:   net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					RemPort:   0,
-					St:        7,
-					TxQueue:   0,
-					RxQueue:   0,
-					UID:       981,
+					Sl: 1315, LocalAddr: 0, LocalPort: 5355, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 981,
 				},
 				&netUDPLine{
-					Sl:        6073,
-					LocalAddr: net.IP{0, 0, 128, 254, 0, 0, 0, 0, 255, 173, 225, 86, 9, 102, 124, 254},
-					LocalPort: 51073,
-					RemAddr:   net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-					RemPort:   0,
-					St:        7,
-					TxQueue:   0,
-					RxQueue:   0,
-					UID:       1000,
+					Sl: 6073, LocalAddr: 0, LocalPort: 51073, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 1000,
 				},
 			},
 			wantErr: false,
diff --git a/proc_maps_test.go b/proc_maps_test.go
new file mode 100644
index 0000000..9bc8e95
--- /dev/null
+++ b/proc_maps_test.go
@@ -0,0 +1,185 @@
+// 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/swaps_test.go b/swaps_test.go
deleted file mode 100644
index 70db6ab..0000000
--- a/swaps_test.go
+++ /dev/null
@@ -1,113 +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.
-
-package procfs
-
-import (
-	"reflect"
-	"testing"
-)
-
-func TestSwaps(t *testing.T) {
-	fs, err := NewFS(procTestFixtures)
-	if err != nil {
-		t.Fatalf("failed to open procfs: %v", err)
-	}
-
-	swaps, err := fs.Swaps()
-	if err != nil {
-		t.Fatalf("failed to get swaps: %v", err)
-	}
-
-	if len(swaps) != 1 {
-		t.Fatalf("expected 1 swap entry, got %d", len(swaps))
-	}
-	swap := swaps[0]
-
-	if swap.Filename != "/dev/dm-2" {
-		t.Errorf("expected swap.Filename /dev/dm-2, got %s", swap.Filename)
-	}
-	if swap.Type != "partition" {
-		t.Errorf("expected swap.Type partition, got %s", swap.Type)
-	}
-	if swap.Size != 131068 {
-		t.Errorf("expected swap.Size 131068, got %d", swap.Size)
-	}
-	if swap.Used != 176 {
-		t.Errorf("expected swap.Used 176, got %d", swap.Used)
-	}
-	if swap.Priority != -2 {
-		t.Errorf("expected swap.Priority -2, got %d", swap.Priority)
-	}
-}
-
-func TestParseSwapString(t *testing.T) {
-	tests := []struct {
-		name    string
-		s       string
-		swap    *Swap
-		invalid bool
-	}{
-		{
-			name:    "device-mapper volume",
-			s:       "/dev/dm-2                               partition       131068  1024    -2",
-			invalid: false,
-			swap: &Swap{
-				Filename: "/dev/dm-2",
-				Type:     "partition",
-				Size:     131068,
-				Used:     1024,
-				Priority: -2,
-			},
-		},
-		{
-			name:    "Swap file",
-			s:       "/foo                                    file            1048572 0       -3",
-			invalid: false,
-			swap: &Swap{
-				Filename: "/foo",
-				Type:     "file",
-				Size:     1048572,
-				Used:     0,
-				Priority: -3,
-			},
-		},
-		{
-			name:    "Invalid number",
-			s:       "/dev/sda2                               partition       hello   world   -2",
-			invalid: true,
-		},
-		{
-			name:    "Not enough fields",
-			s:       "/dev/dm-2                               partition       131068  1024",
-			invalid: true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			swap, err := parseSwapString(tt.s)
-
-			if tt.invalid && err == nil {
-				t.Error("unexpected success")
-			}
-			if !tt.invalid && err != nil {
-				t.Errorf("unexpected error: %v", err)
-			}
-
-			if !reflect.DeepEqual(tt.swap, swap) {
-				t.Errorf("swap:\nwant:\n%+v\nhave:\n%+v", tt.swap, swap)
-			}
-		})
-	}
-}
diff --git a/xfs/parse_test.go b/xfs/parse_test.go
index b799f4b..1774665 100644
--- a/xfs/parse_test.go
+++ b/xfs/parse_test.go
@@ -368,11 +368,6 @@ func TestParseStats(t *testing.T) {
 			s:       "qm 1 2 3 4 5 6 7",
 			invalid: true,
 		},
-		{
-			name:    "qm bad",
-			s:       "qm 1 2 3 4 5 6 7 8 9 10",
-			invalid: true,
-		},
 		{
 			name: "qm OK",
 			s:    "qm 1 2 3 4 5 6 7 8",
@@ -389,23 +384,6 @@ func TestParseStats(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "qm OK",
-			s:    "qm 1 2 3 4 5 6 7 8 9",
-			stats: &xfs.Stats{
-				QuotaManager: xfs.QuotaManagerStats{
-					Reclaims:      1,
-					ReclaimMisses: 2,
-					DquoteDups:    3,
-					CacheMisses:   4,
-					CacheHits:     5,
-					Wants:         6,
-					ShakeReclaims: 7,
-					InactReclaims: 8,
-					Unused:        9,
-				},
-			},
-		},
 		{
 			name:    "abtb2 bad",
 			s:       "abtb2 1 2 3 4 5 6",