Repository: prometheus/procfs
Pull request #328 (2020-09-16 01:04:54)

Add slabinfo support
This patch adds support for reading slab info from /proc/slabinfo. This is to potentially add support in `node_exporter` (see [#1799](https://github.com/prometheus/node_exporter/issues/1799)). One thing to note is that reading from `/proc/slabinfo` requires root/`CAP_SYS_ADMIN` to read, see the [slabinfo](https://man7.org/linux/man-pages/man5/slabinfo.5.html) man page and kernel [slub](https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/tree/Documentation/vm/slub.rst) documentation.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4224ea8..26ed5f5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -34,7 +34,7 @@ jobs:
     steps:
     - checkout
     - run: sudo pip install codespell
-    - run: codespell --skip=".git,./vendor,ttar,fixtures.ttar,./fixtures,go.mod,go.sum" -L uint,packages\',ded,alo,als,te,sie,hart
+    - run: codespell --skip=".git,./vendor,ttar,fixtures.ttar,./fixtures,go.mod,go.sum" -L uint,packages\',ded,alo,als,te,sie,hart,hda
 
 workflows:
   version: 2
@@ -42,9 +42,9 @@ workflows:
     jobs:
     - test:
         command: default
-        name: linux-1-13
+        name: linux-1-15
         os: linux
-        go_version: "1.13"
+        go_version: "1.15"
     - test:
         command: default
         name: linux-1-14
@@ -52,9 +52,9 @@ workflows:
         go_version: "1.14"
     - test:
         command: no-test
-        name: windows-1-13
+        name: windows-1-15
         os: windows
-        go_version: "1.13"
+        go_version: "1.15"
     - test:
         command: no-test
         name: windows-1-14
diff --git a/Makefile.common b/Makefile.common
index 9320176..3ac29c6 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -78,7 +78,7 @@ ifneq ($(shell which gotestsum),)
 endif
 endif
 
-PROMU_VERSION ?= 0.5.0
+PROMU_VERSION ?= 0.7.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 GOLANGCI_LINT :=
@@ -245,10 +245,12 @@ common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
 $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
 	docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
 
+DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
 .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
 common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
 $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
 	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
+	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
 
 .PHONY: common-docker-manifest
 common-docker-manifest:
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..67741f0
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,6 @@
+# Reporting a security issue
+
+The Prometheus security policy, including how to report vulnerabilities, can be
+found here:
+
+https://prometheus.io/docs/operating/security/
diff --git a/bcache/get.go b/bcache/get.go
index c4747ea..0040b28 100644
--- a/bcache/get.go
+++ b/bcache/get.go
@@ -459,7 +459,7 @@ func GetStats(uuidPath string, priorityStats bool) (*Stats, error) {
 		bds.FiveMin.CacheReadaheads = par.readValue("cache_readaheads")
 
 		// dir <uuidPath>/<bds.Name>/stats_total
-		par.setSubDir("stats_total")
+		par.setSubDir(bds.Name, "stats_total")
 		bds.Total.Bypassed = par.readValue("bypassed")
 		bds.Total.CacheBypassHits = par.readValue("cache_bypass_hits")
 		bds.Total.CacheBypassMisses = par.readValue("cache_bypass_misses")
diff --git a/cpuinfo.go b/cpuinfo.go
index b9fb589..1711d73 100644
--- a/cpuinfo.go
+++ b/cpuinfo.go
@@ -313,6 +313,22 @@ func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
 				return nil, err
 			}
 			cpuinfo[i].CPUMHz = v
+		case "physical id":
+			cpuinfo[i].PhysicalID = field[1]
+		case "core id":
+			cpuinfo[i].CoreID = field[1]
+		case "cpu cores":
+			v, err := strconv.ParseUint(field[1], 0, 32)
+			if err != nil {
+				return nil, err
+			}
+			cpuinfo[i].CPUCores = uint(v)
+		case "siblings":
+			v, err := strconv.ParseUint(field[1], 0, 32)
+			if err != nil {
+				return nil, err
+			}
+			cpuinfo[i].Siblings = uint(v)
 		}
 	}
 
diff --git a/cpuinfo_riscvx.go b/cpuinfo_riscvx.go
new file mode 100644
index 0000000..e83c2e2
--- /dev/null
+++ b/cpuinfo_riscvx.go
@@ -0,0 +1,19 @@
+// 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.
+
+// +build linux
+// +build riscv riscv64
+
+package procfs
+
+var parseCPUInfo = parseCPUInfoRISCV
diff --git a/fixtures.ttar b/fixtures.ttar
index 12494d7..1e76173 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -111,7 +111,7 @@ Max core file size        0                    unlimited            bytes
 Max resident set          unlimited            unlimited            bytes
 Max processes             62898                62898                processes
 Max open files            2048                 4096                 files
-Max locked memory         65536                65536                bytes
+Max locked memory         18446744073708503040 18446744073708503040 bytes
 Max address space         8589934592           unlimited            bytes
 Max file locks            unlimited            unlimited            locks
 Max pending signals       62898                62898                signals
@@ -1080,7 +1080,6 @@ internal     : yes
 type         : skcipher
 async        : yes
 blocksize    : 1
-min keysize  : 16
 max keysize  : 32
 ivsize       : 16
 chunksize    : 16
@@ -1839,6 +1838,7 @@ min keysize  : 16
 max keysize  : 32
 
 Mode: 444
+Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/diskstats
 Lines: 52
@@ -2129,6 +2129,24 @@ Lines: 6
        4    1FB3C        0          1282A8F                0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/net/protocols
+Lines: 14
+protocol  size sockets  memory press maxhdr  slab module     cl co di ac io in de sh ss gs se re sp bi br ha uh gp em
+PACKET    1344      2      -1   NI       0   no   kernel      n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n
+PINGv6    1112      0      -1   NI       0   yes  kernel      y  y  y  n  n  y  n  n  y  y  y  y  n  y  y  y  y  y  n
+RAWv6     1112      1      -1   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  n  y  y  y  y  n  n
+UDPLITEv6 1216      0      57   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  n  n  n  y  y  y  n
+UDPv6     1216     10      57   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  n  n  n  y  y  y  n
+TCPv6     2144   1937  1225378   no     320   yes  kernel      y  y  y  y  y  y  y  y  y  y  y  y  y  n  y  y  y  y  y
+UNIX      1024    120      -1   NI       0   yes  kernel      n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n
+UDP-Lite  1024      0      57   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  y  n  n  y  y  y  n
+PING       904      0      -1   NI       0   yes  kernel      y  y  y  n  n  y  n  n  y  y  y  y  n  y  y  y  y  y  n
+RAW        912      0      -1   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  n  y  y  y  y  n  n
+UDP       1024     73      57   NI       0   yes  kernel      y  y  y  n  y  y  y  n  y  y  y  y  y  n  n  y  y  y  n
+TCP       1984  93064  1225378   yes     320   yes  kernel      y  y  y  y  y  y  y  y  y  y  y  y  y  n  y  y  y  y  y
+NETLINK   1040     16      -1   NI       0   no   kernel      n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n  n
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/net/rpc
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2186,10 +2204,25 @@ Lines: 1
 00015c73 00020e76 F0000769 00000000
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/net/tcp
+Lines: 4
+  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+   0: 0500000A: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/tcp6
+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
+# 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
+   0: 0500000A: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
@@ -2292,6 +2325,312 @@ Mode: 644
 Path: fixtures/proc/self
 SymlinkTo: 26231
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/slabinfo
+Lines: 302
+slabinfo - version: 2.1
+# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
+pid_3                375    532    576   28    4 : tunables    0    0    0 : slabdata     19     19      0
+pid_2                  3     28    576   28    4 : tunables    0    0    0 : slabdata      1      1      0
+nvidia_p2p_page_cache      0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+nvidia_pte_cache    9022   9152    368   22    2 : tunables    0    0    0 : slabdata    416    416      0
+nvidia_stack_cache    321    326  12624    2    8 : tunables    0    0    0 : slabdata    163    163      0
+kvm_async_pf           0      0    472   34    4 : tunables    0    0    0 : slabdata      0      0      0
+kvm_vcpu               0      0  15552    2    8 : tunables    0    0    0 : slabdata      0      0      0
+kvm_mmu_page_header      0      0    504   32    4 : tunables    0    0    0 : slabdata      0      0      0
+pte_list_desc          0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+x86_emulator           0      0   3024   10    8 : tunables    0    0    0 : slabdata      0      0      0
+x86_fpu                0      0   4608    7    8 : tunables    0    0    0 : slabdata      0      0      0
+iwl_cmd_pool:0000:04:00.0      0    128    512   32    4 : tunables    0    0    0 : slabdata      4      4      0
+ext4_groupinfo_4k   3719   3740    480   34    4 : tunables    0    0    0 : slabdata    110    110      0
+bio-6                 32     75    640   25    4 : tunables    0    0    0 : slabdata      3      3      0
+bio-5                 16     48   1344   24    8 : tunables    0    0    0 : slabdata      2      2      0
+bio-4                 17     92   1408   23    8 : tunables    0    0    0 : slabdata      4      4      0
+fat_inode_cache        0      0   1056   31    8 : tunables    0    0    0 : slabdata      0      0      0
+fat_cache              0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+ovl_aio_req            0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+ovl_inode              0      0   1000   32    8 : tunables    0    0    0 : slabdata      0      0      0
+squashfs_inode_cache      0      0   1088   30    8 : tunables    0    0    0 : slabdata      0      0      0
+fuse_request           0      0    472   34    4 : tunables    0    0    0 : slabdata      0      0      0
+fuse_inode             0      0   1152   28    8 : tunables    0    0    0 : slabdata      0      0      0
+xfs_dqtrx              0      0    864   37    8 : tunables    0    0    0 : slabdata      0      0      0
+xfs_dquot              0      0    832   39    8 : tunables    0    0    0 : slabdata      0      0      0
+xfs_buf                0      0    768   21    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_bui_item           0      0    544   30    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_bud_item           0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_cui_item           0      0    768   21    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_cud_item           0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_rui_item           0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
+xfs_rud_item           0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_icr                0      0    520   31    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_ili                0      0    528   31    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_inode              0      0   1344   24    8 : tunables    0    0    0 : slabdata      0      0      0
+xfs_efi_item           0      0    768   21    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_efd_item           0      0    776   21    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_buf_item           0      0    608   26    4 : tunables    0    0    0 : slabdata      0      0      0
+xf_trans               0      0    568   28    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_ifork              0      0    376   21    2 : tunables    0    0    0 : slabdata      0      0      0
+xfs_da_state           0      0    816   20    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_btree_cur          0      0    560   29    4 : tunables    0    0    0 : slabdata      0      0      0
+xfs_bmap_free_item      0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+xfs_log_ticket         0      0    520   31    4 : tunables    0    0    0 : slabdata      0      0      0
+nfs_direct_cache       0      0    560   29    4 : tunables    0    0    0 : slabdata      0      0      0
+nfs_commit_data        4     28   1152   28    8 : tunables    0    0    0 : slabdata      1      1      0
+nfs_write_data        32     50   1280   25    8 : tunables    0    0    0 : slabdata      2      2      0
+nfs_read_data          0      0   1280   25    8 : tunables    0    0    0 : slabdata      0      0      0
+nfs_inode_cache        0      0   1408   23    8 : tunables    0    0    0 : slabdata      0      0      0
+nfs_page               0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+rpc_inode_cache        0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
+rpc_buffers            8     13   2496   13    8 : tunables    0    0    0 : slabdata      1      1      0
+rpc_tasks              8     25    640   25    4 : tunables    0    0    0 : slabdata      1      1      0
+fscache_cookie_jar      1     35    464   35    4 : tunables    0    0    0 : slabdata      1      1      0
+jfs_mp                32     35    464   35    4 : tunables    0    0    0 : slabdata      1      1      0
+jfs_ip                 0      0   1592   20    8 : tunables    0    0    0 : slabdata      0      0      0
+reiser_inode_cache      0      0   1096   29    8 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_end_io_wq        0      0    464   35    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_prelim_ref       0      0    424   38    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_delayed_extent_op      0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_delayed_data_ref      0      0    448   36    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_delayed_tree_ref      0      0    440   37    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_delayed_ref_head      0      0    480   34    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_inode_defrag      0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_delayed_node      0      0    648   25    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_ordered_extent      0      0    752   21    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_extent_map       0      0    480   34    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_extent_state      0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+bio-3                 35     92    704   23    4 : tunables    0    0    0 : slabdata      4      4      0
+btrfs_extent_buffer      0      0    600   27    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_free_space_bitmap      0      0  12288    2    8 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_free_space       0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_path             0      0    448   36    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_trans_handle      0      0    440   37    4 : tunables    0    0    0 : slabdata      0      0      0
+btrfs_inode            0      0   1496   21    8 : tunables    0    0    0 : slabdata      0      0      0
+ext4_inode_cache   84136  84755   1400   23    8 : tunables    0    0    0 : slabdata   3685   3685      0
+ext4_free_data        22     80    392   20    2 : tunables    0    0    0 : slabdata      4      4      0
+ext4_allocation_context      0     70    464   35    4 : tunables    0    0    0 : slabdata      2      2      0
+ext4_prealloc_space     24     74    440   37    4 : tunables    0    0    0 : slabdata      2      2      0
+ext4_system_zone     267    273    376   21    2 : tunables    0    0    0 : slabdata     13     13      0
+ext4_io_end_vec        0     88    368   22    2 : tunables    0    0    0 : slabdata      4      4      0
+ext4_io_end            0     80    400   20    2 : tunables    0    0    0 : slabdata      4      4      0
+ext4_bio_post_read_ctx    128    147    384   21    2 : tunables    0    0    0 : slabdata      7      7      0
+ext4_pending_reservation      0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+ext4_extent_status  79351  79422    376   21    2 : tunables    0    0    0 : slabdata   3782   3782      0
+jbd2_transaction_s     44    100    640   25    4 : tunables    0    0    0 : slabdata      4      4      0
+jbd2_inode          6785   6840    400   20    2 : tunables    0    0    0 : slabdata    342    342      0
+jbd2_journal_handle      0     80    392   20    2 : tunables    0    0    0 : slabdata      4      4      0
+jbd2_journal_head    824   1944    448   36    4 : tunables    0    0    0 : slabdata     54     54      0
+jbd2_revoke_table_s      4     23    352   23    2 : tunables    0    0    0 : slabdata      1      1      0
+jbd2_revoke_record_s      0    156    416   39    4 : tunables    0    0    0 : slabdata      4      4      0
+ext2_inode_cache       0      0   1144   28    8 : tunables    0    0    0 : slabdata      0      0      0
+mbcache                0      0    392   20    2 : tunables    0    0    0 : slabdata      0      0      0
+dm_thin_new_mapping      0    152    424   38    4 : tunables    0    0    0 : slabdata      4      4      0
+dm_snap_pending_exception      0      0    464   35    4 : tunables    0    0    0 : slabdata      0      0      0
+dm_exception           0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+dm_dirty_log_flush_entry      0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+dm_bio_prison_cell_v2      0      0    432   37    4 : tunables    0    0    0 : slabdata      0      0      0
+dm_bio_prison_cell      0    148    432   37    4 : tunables    0    0    0 : slabdata      4      4      0
+kcopyd_job             0      8   3648    8    8 : tunables    0    0    0 : slabdata      1      1      0
+io                     0     32    512   32    4 : tunables    0    0    0 : slabdata      1      1      0
+dm_uevent              0      0   3224   10    8 : tunables    0    0    0 : slabdata      0      0      0
+dax_cache              1     28   1152   28    8 : tunables    0    0    0 : slabdata      1      1      0
+aic94xx_ascb           0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+aic94xx_dma_token      0      0    384   21    2 : tunables    0    0    0 : slabdata      0      0      0
+asd_sas_event          0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+sas_task               0      0    704   23    4 : tunables    0    0    0 : slabdata      0      0      0
+qla2xxx_srbs           0      0    832   39    8 : tunables    0    0    0 : slabdata      0      0      0
+sd_ext_cdb             2     22    368   22    2 : tunables    0    0    0 : slabdata      1      1      0
+scsi_sense_cache     258    288    512   32    4 : tunables    0    0    0 : slabdata      9      9      0
+virtio_scsi_cmd       64     75    640   25    4 : tunables    0    0    0 : slabdata      3      3      0
+L2TP/IPv6              0      0   1536   21    8 : tunables    0    0    0 : slabdata      0      0      0
+L2TP/IP                0      0   1408   23    8 : tunables    0    0    0 : slabdata      0      0      0
+ip6-frags              0      0    520   31    4 : tunables    0    0    0 : slabdata      0      0      0
+fib6_nodes             5     32    512   32    4 : tunables    0    0    0 : slabdata      1      1      0
+ip6_dst_cache          4     25    640   25    4 : tunables    0    0    0 : slabdata      1      1      0
+ip6_mrt_cache          0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+PINGv6                 0      0   1600   20    8 : tunables    0    0    0 : slabdata      0      0      0
+RAWv6                 25     40   1600   20    8 : tunables    0    0    0 : slabdata      2      2      0
+UDPLITEv6              0      0   1728   18    8 : tunables    0    0    0 : slabdata      0      0      0
+UDPv6                  3     54   1728   18    8 : tunables    0    0    0 : slabdata      3      3      0
+tw_sock_TCPv6          0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+request_sock_TCPv6      0      0    632   25    4 : tunables    0    0    0 : slabdata      0      0      0
+TCPv6                  0     33   2752   11    8 : tunables    0    0    0 : slabdata      3      3      0
+uhci_urb_priv          0      0    392   20    2 : tunables    0    0    0 : slabdata      0      0      0
+sgpool-128             2     14   4544    7    8 : tunables    0    0    0 : slabdata      2      2      0
+sgpool-64              2     13   2496   13    8 : tunables    0    0    0 : slabdata      1      1      0
+sgpool-32              2     44   1472   22    8 : tunables    0    0    0 : slabdata      2      2      0
+sgpool-16              2     68    960   34    8 : tunables    0    0    0 : slabdata      2      2      0
+sgpool-8               2     46    704   23    4 : tunables    0    0    0 : slabdata      2      2      0
+btree_node             0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+bfq_io_cq              0      0    488   33    4 : tunables    0    0    0 : slabdata      0      0      0
+bfq_queue              0      0    848   38    8 : tunables    0    0    0 : slabdata      0      0      0
+mqueue_inode_cache      1     24   1344   24    8 : tunables    0    0    0 : slabdata      1      1      0
+isofs_inode_cache      0      0    968   33    8 : tunables    0    0    0 : slabdata      0      0      0
+io_kiocb               0      0    640   25    4 : tunables    0    0    0 : slabdata      0      0      0
+kioctx                 0     30   1088   30    8 : tunables    0    0    0 : slabdata      1      1      0
+aio_kiocb              0     28    576   28    4 : tunables    0    0    0 : slabdata      1      1      0
+userfaultfd_ctx_cache      0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+fanotify_path_event      0      0    392   20    2 : tunables    0    0    0 : slabdata      0      0      0
+fanotify_fid_event      0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+fsnotify_mark          0      0    408   20    2 : tunables    0    0    0 : slabdata      0      0      0
+dnotify_mark           0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+dnotify_struct         0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+dio                    0      0   1088   30    8 : tunables    0    0    0 : slabdata      0      0      0
+bio-2                  4     25    640   25    4 : tunables    0    0    0 : slabdata      1      1      0
+fasync_cache           0      0    384   21    2 : tunables    0    0    0 : slabdata      0      0      0
+audit_tree_mark        0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+pid_namespace         30     34    480   34    4 : tunables    0    0    0 : slabdata      1      1      0
+posix_timers_cache      0     27    592   27    4 : tunables    0    0    0 : slabdata      1      1      0
+iommu_devinfo         24     32    512   32    4 : tunables    0    0    0 : slabdata      1      1      0
+iommu_domain          10     10   3264   10    8 : tunables    0    0    0 : slabdata      1      1      0
+iommu_iova          8682   8748    448   36    4 : tunables    0    0    0 : slabdata    243    243      0
+UNIX                 529    814   1472   22    8 : tunables    0    0    0 : slabdata     37     37      0
+ip4-frags              0      0    536   30    4 : tunables    0    0    0 : slabdata      0      0      0
+ip_mrt_cache           0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+UDP-Lite               0      0   1536   21    8 : tunables    0    0    0 : slabdata      0      0      0
+tcp_bind_bucket        7    128    512   32    4 : tunables    0    0    0 : slabdata      4      4      0
+inet_peer_cache        0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+xfrm_dst_cache         0      0    704   23    4 : tunables    0    0    0 : slabdata      0      0      0
+xfrm_state             0      0   1152   28    8 : tunables    0    0    0 : slabdata      0      0      0
+ip_fib_trie            7     21    384   21    2 : tunables    0    0    0 : slabdata      1      1      0
+ip_fib_alias           9     20    392   20    2 : tunables    0    0    0 : slabdata      1      1      0
+ip_dst_cache          27     84    576   28    4 : tunables    0    0    0 : slabdata      3      3      0
+PING                   0      0   1408   23    8 : tunables    0    0    0 : slabdata      0      0      0
+RAW                   32     46   1408   23    8 : tunables    0    0    0 : slabdata      2      2      0
+UDP                   11    168   1536   21    8 : tunables    0    0    0 : slabdata      8      8      0
+tw_sock_TCP            1     56    576   28    4 : tunables    0    0    0 : slabdata      2      2      0
+request_sock_TCP       0     25    632   25    4 : tunables    0    0    0 : slabdata      1      1      0
+TCP                   10     60   2624   12    8 : tunables    0    0    0 : slabdata      5      5      0
+hugetlbfs_inode_cache      2     35    928   35    8 : tunables    0    0    0 : slabdata      1      1      0
+dquot                  0      0    640   25    4 : tunables    0    0    0 : slabdata      0      0      0
+bio-1                 32     46    704   23    4 : tunables    0    0    0 : slabdata      2      2      0
+eventpoll_pwq        409    600    408   20    2 : tunables    0    0    0 : slabdata     30     30      0
+eventpoll_epi        408    672    576   28    4 : tunables    0    0    0 : slabdata     24     24      0
+inotify_inode_mark     58    195    416   39    4 : tunables    0    0    0 : slabdata      5      5      0
+scsi_data_buffer       0      0    360   22    2 : tunables    0    0    0 : slabdata      0      0      0
+bio_crypt_ctx        128    147    376   21    2 : tunables    0    0    0 : slabdata      7      7      0
+request_queue         29     39   2408   13    8 : tunables    0    0    0 : slabdata      3      3      0
+blkdev_ioc            81    148    440   37    4 : tunables    0    0    0 : slabdata      4      4      0
+bio-0                125    200    640   25    4 : tunables    0    0    0 : slabdata      8      8      0
+biovec-max           166    196   4544    7    8 : tunables    0    0    0 : slabdata     28     28      0
+biovec-128             0     52   2496   13    8 : tunables    0    0    0 : slabdata      4      4      0
+biovec-64              0     88   1472   22    8 : tunables    0    0    0 : slabdata      4      4      0
+biovec-16              0     92    704   23    4 : tunables    0    0    0 : slabdata      4      4      0
+bio_integrity_payload      4     28    576   28    4 : tunables    0    0    0 : slabdata      1      1      0
+khugepaged_mm_slot     59    180    448   36    4 : tunables    0    0    0 : slabdata      5      5      0
+ksm_mm_slot            0      0    384   21    2 : tunables    0    0    0 : slabdata      0      0      0
+ksm_stable_node        0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+ksm_rmap_item          0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+user_namespace         2     37    864   37    8 : tunables    0    0    0 : slabdata      1      1      0
+uid_cache              5     28    576   28    4 : tunables    0    0    0 : slabdata      1      1      0
+dmaengine-unmap-256      1     13   2496   13    8 : tunables    0    0    0 : slabdata      1      1      0
+dmaengine-unmap-128      1     22   1472   22    8 : tunables    0    0    0 : slabdata      1      1      0
+dmaengine-unmap-16      1     28    576   28    4 : tunables    0    0    0 : slabdata      1      1      0
+dmaengine-unmap-2      1     36    448   36    4 : tunables    0    0    0 : slabdata      1      1      0
+audit_buffer           0     22    360   22    2 : tunables    0    0    0 : slabdata      1      1      0
+sock_inode_cache     663   1170   1216   26    8 : tunables    0    0    0 : slabdata     45     45      0
+skbuff_ext_cache       0      0    576   28    4 : tunables    0    0    0 : slabdata      0      0      0
+skbuff_fclone_cache      1     72    896   36    8 : tunables    0    0    0 : slabdata      2      2      0
+skbuff_head_cache      3    650    640   25    4 : tunables    0    0    0 : slabdata     26     26      0
+configfs_dir_cache      7     38    424   38    4 : tunables    0    0    0 : slabdata      1      1      0
+file_lock_cache       27    116    552   29    4 : tunables    0    0    0 : slabdata      4      4      0
+file_lock_ctx        106    120    392   20    2 : tunables    0    0    0 : slabdata      6      6      0
+fsnotify_mark_connector     52     66    368   22    2 : tunables    0    0    0 : slabdata      3      3      0
+net_namespace          1      6   5312    6    8 : tunables    0    0    0 : slabdata      1      1      0
+task_delay_info      784   1560    416   39    4 : tunables    0    0    0 : slabdata     40     40      0
+taskstats             45     92    688   23    4 : tunables    0    0    0 : slabdata      4      4      0
+proc_dir_entry       678    682    528   31    4 : tunables    0    0    0 : slabdata     22     22      0
+pde_opener             0    189    376   21    2 : tunables    0    0    0 : slabdata      9      9      0
+proc_inode_cache    7150   8250    992   33    8 : tunables    0    0    0 : slabdata    250    250      0
+seq_file              60    735    456   35    4 : tunables    0    0    0 : slabdata     21     21      0
+sigqueue               0    156    416   39    4 : tunables    0    0    0 : slabdata      4      4      0
+bdev_cache            36     78   1216   26    8 : tunables    0    0    0 : slabdata      3      3      0
+shmem_inode_cache   1599   2208   1016   32    8 : tunables    0    0    0 : slabdata     69     69      0
+kernfs_iattrs_cache   1251   1254    424   38    4 : tunables    0    0    0 : slabdata     33     33      0
+kernfs_node_cache  52898  52920    464   35    4 : tunables    0    0    0 : slabdata   1512   1512      0
+mnt_cache             42     46    704   23    4 : tunables    0    0    0 : slabdata      2      2      0
+filp                4314   6371    704   23    4 : tunables    0    0    0 : slabdata    277    277      0
+inode_cache        28695  29505    920   35    8 : tunables    0    0    0 : slabdata    843    843      0
+dentry            166069 169074    528   31    4 : tunables    0    0    0 : slabdata   5454   5454      0
+names_cache            0     35   4544    7    8 : tunables    0    0    0 : slabdata      5      5      0
+hashtab_node           0      0    360   22    2 : tunables    0    0    0 : slabdata      0      0      0
+ebitmap_node           0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
+avtab_extended_perms      0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+avtab_node             0      0    360   22    2 : tunables    0    0    0 : slabdata      0      0      0
+avc_xperms_data        0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+avc_xperms_decision_node      0      0    384   21    2 : tunables    0    0    0 : slabdata      0      0      0
+avc_xperms_node        0      0    392   20    2 : tunables    0    0    0 : slabdata      0      0      0
+avc_node              37     40    408   20    2 : tunables    0    0    0 : slabdata      2      2      0
+iint_cache             0      0    448   36    4 : tunables    0    0    0 : slabdata      0      0      0
+lsm_inode_cache   122284 122340    392   20    2 : tunables    0    0    0 : slabdata   6117   6117      0
+lsm_file_cache      4266   4485    352   23    2 : tunables    0    0    0 : slabdata    195    195      0
+key_jar                8     25    640   25    4 : tunables    0    0    0 : slabdata      1      1      0
+buffer_head       255622 257076    440   37    4 : tunables    0    0    0 : slabdata   6948   6948      0
+uts_namespace          0      0    776   21    4 : tunables    0    0    0 : slabdata      0      0      0
+nsproxy               31     40    408   20    2 : tunables    0    0    0 : slabdata      2      2      0
+vm_area_struct     39115  43214    528   31    4 : tunables    0    0    0 : slabdata   1394   1394      0
+mm_struct             96    529   1408   23    8 : tunables    0    0    0 : slabdata     23     23      0
+fs_cache             102    756    448   36    4 : tunables    0    0    0 : slabdata     21     21      0
+files_cache          102    588   1152   28    8 : tunables    0    0    0 : slabdata     21     21      0
+signal_cache         266    672   1536   21    8 : tunables    0    0    0 : slabdata     32     32      0
+sighand_cache        266    507   2496   13    8 : tunables    0    0    0 : slabdata     39     39      0
+task_struct          783    963  10240    3    8 : tunables    0    0    0 : slabdata    321    321      0
+cred_jar             364    952    576   28    4 : tunables    0    0    0 : slabdata     34     34      0
+anon_vma_chain     63907  67821    416   39    4 : tunables    0    0    0 : slabdata   1739   1739      0
+anon_vma           25891  28899    416   39    4 : tunables    0    0    0 : slabdata    741    741      0
+pid                  408    992    512   32    4 : tunables    0    0    0 : slabdata     31     31      0
+Acpi-Operand        6682   6740    408   20    2 : tunables    0    0    0 : slabdata    337    337      0
+Acpi-ParseExt          0     39    416   39    4 : tunables    0    0    0 : slabdata      1      1      0
+Acpi-Parse             0     80    392   20    2 : tunables    0    0    0 : slabdata      4      4      0
+Acpi-State             0     78    416   39    4 : tunables    0    0    0 : slabdata      2      2      0
+Acpi-Namespace      3911   3948    384   21    2 : tunables    0    0    0 : slabdata    188    188      0
+trace_event_file    2638   2660    424   38    4 : tunables    0    0    0 : slabdata     70     70      0
+ftrace_event_field   6592   6594    384   21    2 : tunables    0    0    0 : slabdata    314    314      0
+pool_workqueue        41     64   1024   32    8 : tunables    0    0    0 : slabdata      2      2      0
+radix_tree_node    21638  24045    912   35    8 : tunables    0    0    0 : slabdata    687    687      0
+task_group            48     78   1216   26    8 : tunables    0    0    0 : slabdata      3      3      0
+vmap_area           4411   4680    400   20    2 : tunables    0    0    0 : slabdata    234    234      0
+dma-kmalloc-8k         0      0  24576    1    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-4k         0      0  12288    2    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-2k         0      0   6144    5    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-1k         0      0   3072   10    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-512        0      0   1536   21    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-256        0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-128        0      0    640   25    4 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-64         0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-32         0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-16         0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-8          0      0    344   23    2 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-192        0      0    528   31    4 : tunables    0    0    0 : slabdata      0      0      0
+dma-kmalloc-96         0      0    432   37    4 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-8k         0      0  24576    1    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-4k         0      0  12288    2    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-2k         0      0   6144    5    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-1k         0      0   3072   10    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-512        0      0   1536   21    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-256        0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-192        0      0    528   31    4 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-128       31     75    640   25    4 : tunables    0    0    0 : slabdata      3      3      0
+kmalloc-rcl-96      3371   3626    432   37    4 : tunables    0    0    0 : slabdata     98     98      0
+kmalloc-rcl-64      2080   2272    512   32    4 : tunables    0    0    0 : slabdata     71     71      0
+kmalloc-rcl-32         0      0    416   39    4 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-16         0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-rcl-8          0      0    344   23    2 : tunables    0    0    0 : slabdata      0      0      0
+kmalloc-8k           133    140  24576    1    8 : tunables    0    0    0 : slabdata    140    140      0
+kmalloc-4k           403    444  12288    2    8 : tunables    0    0    0 : slabdata    222    222      0
+kmalloc-2k          2391   2585   6144    5    8 : tunables    0    0    0 : slabdata    517    517      0
+kmalloc-1k          2163   2420   3072   10    8 : tunables    0    0    0 : slabdata    242    242      0
+kmalloc-512         2972   3633   1536   21    8 : tunables    0    0    0 : slabdata    173    173      0
+kmalloc-256         1841   1856   1024   32    8 : tunables    0    0    0 : slabdata     58     58      0
+kmalloc-192         2165   2914    528   31    4 : tunables    0    0    0 : slabdata     94     94      0
+kmalloc-128         1137   1175    640   25    4 : tunables    0    0    0 : slabdata     47     47      0
+kmalloc-96          1925   2590    432   37    4 : tunables    0    0    0 : slabdata     70     70      0
+kmalloc-64          9433  10688    512   32    4 : tunables    0    0    0 : slabdata    334    334      0
+kmalloc-32          9098  10062    416   39    4 : tunables    0    0    0 : slabdata    258    258      0
+kmalloc-16         10914  10956    368   22    2 : tunables    0    0    0 : slabdata    498    498      0
+kmalloc-8           7576   7705    344   23    2 : tunables    0    0    0 : slabdata    335    335      0
+kmem_cache_node      904    928    512   32    4 : tunables    0    0    0 : slabdata     29     29      0
+kmem_cache           904    936    832   39    8 : tunables    0    0    0 : slabdata     24     24      0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/stat
 Lines: 16
 cpu  301854 612 111922 8979004 3552 2 3944 0 0 0
@@ -4639,6 +4978,35 @@ Mode: 644
 Directory: fixtures/sys/devices/system
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/node
+Mode: 775
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/node/node1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/node/node1/vmstat
+Lines: 6
+nr_free_pages 1
+nr_zone_inactive_anon 2
+nr_zone_active_anon 3
+nr_zone_inactive_file 4
+nr_zone_active_file 5
+nr_zone_unevictable 6
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/node/node2
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/node/node2/vmstat
+Lines: 6
+nr_free_pages 7
+nr_zone_inactive_anon 8
+nr_zone_active_anon 9
+nr_zone_inactive_file 10
+nr_zone_active_file 11
+nr_zone_unevictable 12
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/clocksource
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/mdstat.go b/mdstat.go
index 98e37aa..a5a4ba5 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -22,8 +22,9 @@ import (
 )
 
 var (
-	statusLineRE   = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
-	recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
+	statusLineRE      = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
+	recoveryLineRE    = regexp.MustCompile(`\((\d+)/\d+\)`)
+	componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`)
 )
 
 // MDStat holds info parsed from /proc/mdstat.
@@ -44,6 +45,8 @@ type MDStat struct {
 	BlocksTotal int64
 	// Number of blocks on the device that are in sync.
 	BlocksSynced int64
+	// Name of md component devices
+	Devices []string
 }
 
 // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
@@ -140,6 +143,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			DisksTotal:    total,
 			BlocksTotal:   size,
 			BlocksSynced:  syncedBlocks,
+			Devices:       evalComponentDevices(deviceFields),
 		})
 	}
 
@@ -195,3 +199,18 @@ func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) {
 
 	return syncedBlocks, nil
 }
+
+func evalComponentDevices(deviceFields []string) []string {
+	mdComponentDevices := make([]string, 0)
+	if len(deviceFields) > 3 {
+		for _, field := range deviceFields[4:] {
+			match := componentDeviceRE.FindStringSubmatch(field)
+			if match == nil {
+				continue
+			}
+			mdComponentDevices = append(mdComponentDevices, match[1])
+		}
+	}
+
+	return mdComponentDevices
+}
diff --git a/meminfo.go b/meminfo.go
index 50dab4b..2146734 100644
--- a/meminfo.go
+++ b/meminfo.go
@@ -28,9 +28,9 @@ import (
 type Meminfo struct {
 	// Total usable ram (i.e. physical ram minus a few reserved
 	// bits and the kernel binary code)
-	MemTotal uint64
+	MemTotal *uint64
 	// The sum of LowFree+HighFree
-	MemFree uint64
+	MemFree *uint64
 	// An estimate of how much memory is available for starting
 	// new applications, without swapping. Calculated from
 	// MemFree, SReclaimable, the size of the file LRU lists, and
@@ -39,59 +39,59 @@ type Meminfo struct {
 	// well, and that not all reclaimable slab will be
 	// reclaimable, due to items being in use. The impact of those
 	// factors will vary from system to system.
-	MemAvailable uint64
+	MemAvailable *uint64
 	// Relatively temporary storage for raw disk blocks shouldn't
 	// get tremendously large (20MB or so)
-	Buffers uint64
-	Cached  uint64
+	Buffers *uint64
+	Cached  *uint64
 	// Memory that once was swapped out, is swapped back in but
 	// still also is in the swapfile (if memory is needed it
 	// doesn't need to be swapped out AGAIN because it is already
 	// in the swapfile. This saves I/O)
-	SwapCached uint64
+	SwapCached *uint64
 	// Memory that has been used more recently and usually not
 	// reclaimed unless absolutely necessary.
-	Active uint64
+	Active *uint64
 	// Memory which has been less recently used.  It is more
 	// eligible to be reclaimed for other purposes
-	Inactive     uint64
-	ActiveAnon   uint64
-	InactiveAnon uint64
-	ActiveFile   uint64
-	InactiveFile uint64
-	Unevictable  uint64
-	Mlocked      uint64
+	Inactive     *uint64
+	ActiveAnon   *uint64
+	InactiveAnon *uint64
+	ActiveFile   *uint64
+	InactiveFile *uint64
+	Unevictable  *uint64
+	Mlocked      *uint64
 	// total amount of swap space available
-	SwapTotal uint64
+	SwapTotal *uint64
 	// Memory which has been evicted from RAM, and is temporarily
 	// on the disk
-	SwapFree uint64
+	SwapFree *uint64
 	// Memory which is waiting to get written back to the disk
-	Dirty uint64
+	Dirty *uint64
 	// Memory which is actively being written back to the disk
-	Writeback uint64
+	Writeback *uint64
 	// Non-file backed pages mapped into userspace page tables
-	AnonPages uint64
+	AnonPages *uint64
 	// files which have been mapped, such as libraries
-	Mapped uint64
-	Shmem  uint64
+	Mapped *uint64
+	Shmem  *uint64
 	// in-kernel data structures cache
-	Slab uint64
+	Slab *uint64
 	// Part of Slab, that might be reclaimed, such as caches
-	SReclaimable uint64
+	SReclaimable *uint64
 	// Part of Slab, that cannot be reclaimed on memory pressure
-	SUnreclaim  uint64
-	KernelStack uint64
+	SUnreclaim  *uint64
+	KernelStack *uint64
 	// amount of memory dedicated to the lowest level of page
 	// tables.
-	PageTables uint64
+	PageTables *uint64
 	// NFS pages sent to the server, but not yet committed to
 	// stable storage
-	NFSUnstable uint64
+	NFSUnstable *uint64
 	// Memory used for block device "bounce buffers"
-	Bounce uint64
+	Bounce *uint64
 	// Memory used by FUSE for temporary writeback buffers
-	WritebackTmp uint64
+	WritebackTmp *uint64
 	// Based on the overcommit ratio ('vm.overcommit_ratio'),
 	// this is the total amount of  memory currently available to
 	// be allocated on the system. This limit is only adhered to
@@ -105,7 +105,7 @@ type Meminfo struct {
 	// yield a CommitLimit of 7.3G.
 	// For more details, see the memory overcommit documentation
 	// in vm/overcommit-accounting.
-	CommitLimit uint64
+	CommitLimit *uint64
 	// The amount of memory presently allocated on the system.
 	// The committed memory is a sum of all of the memory which
 	// has been allocated by processes, even if it has not been
@@ -119,27 +119,27 @@ type Meminfo struct {
 	// This is useful if one needs to guarantee that processes will
 	// not fail due to lack of memory once that memory has been
 	// successfully allocated.
-	CommittedAS uint64
+	CommittedAS *uint64
 	// total size of vmalloc memory area
-	VmallocTotal uint64
+	VmallocTotal *uint64
 	// amount of vmalloc area which is used
-	VmallocUsed uint64
+	VmallocUsed *uint64
 	// largest contiguous block of vmalloc area which is free
-	VmallocChunk      uint64
-	HardwareCorrupted uint64
-	AnonHugePages     uint64
-	ShmemHugePages    uint64
-	ShmemPmdMapped    uint64
-	CmaTotal          uint64
-	CmaFree           uint64
-	HugePagesTotal    uint64
-	HugePagesFree     uint64
-	HugePagesRsvd     uint64
-	HugePagesSurp     uint64
-	Hugepagesize      uint64
-	DirectMap4k       uint64
-	DirectMap2M       uint64
-	DirectMap1G       uint64
+	VmallocChunk      *uint64
+	HardwareCorrupted *uint64
+	AnonHugePages     *uint64
+	ShmemHugePages    *uint64
+	ShmemPmdMapped    *uint64
+	CmaTotal          *uint64
+	CmaFree           *uint64
+	HugePagesTotal    *uint64
+	HugePagesFree     *uint64
+	HugePagesRsvd     *uint64
+	HugePagesSurp     *uint64
+	Hugepagesize      *uint64
+	DirectMap4k       *uint64
+	DirectMap2M       *uint64
+	DirectMap1G       *uint64
 }
 
 // Meminfo returns an information about current kernel/system memory statistics.
@@ -175,101 +175,101 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) {
 
 		switch fields[0] {
 		case "MemTotal:":
-			m.MemTotal = v
+			m.MemTotal = &v
 		case "MemFree:":
-			m.MemFree = v
+			m.MemFree = &v
 		case "MemAvailable:":
-			m.MemAvailable = v
+			m.MemAvailable = &v
 		case "Buffers:":
-			m.Buffers = v
+			m.Buffers = &v
 		case "Cached:":
-			m.Cached = v
+			m.Cached = &v
 		case "SwapCached:":
-			m.SwapCached = v
+			m.SwapCached = &v
 		case "Active:":
-			m.Active = v
+			m.Active = &v
 		case "Inactive:":
-			m.Inactive = v
+			m.Inactive = &v
 		case "Active(anon):":
-			m.ActiveAnon = v
+			m.ActiveAnon = &v
 		case "Inactive(anon):":
-			m.InactiveAnon = v
+			m.InactiveAnon = &v
 		case "Active(file):":
-			m.ActiveFile = v
+			m.ActiveFile = &v
 		case "Inactive(file):":
-			m.InactiveFile = v
+			m.InactiveFile = &v
 		case "Unevictable:":
-			m.Unevictable = v
+			m.Unevictable = &v
 		case "Mlocked:":
-			m.Mlocked = v
+			m.Mlocked = &v
 		case "SwapTotal:":
-			m.SwapTotal = v
+			m.SwapTotal = &v
 		case "SwapFree:":
-			m.SwapFree = v
+			m.SwapFree = &v
 		case "Dirty:":
-			m.Dirty = v
+			m.Dirty = &v
 		case "Writeback:":
-			m.Writeback = v
+			m.Writeback = &v
 		case "AnonPages:":
-			m.AnonPages = v
+			m.AnonPages = &v
 		case "Mapped:":
-			m.Mapped = v
+			m.Mapped = &v
 		case "Shmem:":
-			m.Shmem = v
+			m.Shmem = &v
 		case "Slab:":
-			m.Slab = v
+			m.Slab = &v
 		case "SReclaimable:":
-			m.SReclaimable = v
+			m.SReclaimable = &v
 		case "SUnreclaim:":
-			m.SUnreclaim = v
+			m.SUnreclaim = &v
 		case "KernelStack:":
-			m.KernelStack = v
+			m.KernelStack = &v
 		case "PageTables:":
-			m.PageTables = v
+			m.PageTables = &v
 		case "NFS_Unstable:":
-			m.NFSUnstable = v
+			m.NFSUnstable = &v
 		case "Bounce:":
-			m.Bounce = v
+			m.Bounce = &v
 		case "WritebackTmp:":
-			m.WritebackTmp = v
+			m.WritebackTmp = &v
 		case "CommitLimit:":
-			m.CommitLimit = v
+			m.CommitLimit = &v
 		case "Committed_AS:":
-			m.CommittedAS = v
+			m.CommittedAS = &v
 		case "VmallocTotal:":
-			m.VmallocTotal = v
+			m.VmallocTotal = &v
 		case "VmallocUsed:":
-			m.VmallocUsed = v
+			m.VmallocUsed = &v
 		case "VmallocChunk:":
-			m.VmallocChunk = v
+			m.VmallocChunk = &v
 		case "HardwareCorrupted:":
-			m.HardwareCorrupted = v
+			m.HardwareCorrupted = &v
 		case "AnonHugePages:":
-			m.AnonHugePages = v
+			m.AnonHugePages = &v
 		case "ShmemHugePages:":
-			m.ShmemHugePages = v
+			m.ShmemHugePages = &v
 		case "ShmemPmdMapped:":
-			m.ShmemPmdMapped = v
+			m.ShmemPmdMapped = &v
 		case "CmaTotal:":
-			m.CmaTotal = v
+			m.CmaTotal = &v
 		case "CmaFree:":
-			m.CmaFree = v
+			m.CmaFree = &v
 		case "HugePages_Total:":
-			m.HugePagesTotal = v
+			m.HugePagesTotal = &v
 		case "HugePages_Free:":
-			m.HugePagesFree = v
+			m.HugePagesFree = &v
 		case "HugePages_Rsvd:":
-			m.HugePagesRsvd = v
+			m.HugePagesRsvd = &v
 		case "HugePages_Surp:":
-			m.HugePagesSurp = v
+			m.HugePagesSurp = &v
 		case "Hugepagesize:":
-			m.Hugepagesize = v
+			m.Hugepagesize = &v
 		case "DirectMap4k:":
-			m.DirectMap4k = v
+			m.DirectMap4k = &v
 		case "DirectMap2M:":
-			m.DirectMap2M = v
+			m.DirectMap2M = &v
 		case "DirectMap1G:":
-			m.DirectMap1G = v
+			m.DirectMap1G = &v
 		}
 	}
 
diff --git a/mountstats.go b/mountstats.go
index 861ced9..f7a828b 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -338,12 +338,12 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 		if len(ss) == 0 {
 			break
 		}
-		if len(ss) < 2 {
-			return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
-		}
 
 		switch ss[0] {
 		case fieldOpts:
+			if len(ss) < 2 {
+				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
+			}
 			if stats.Opts == nil {
 				stats.Opts = map[string]string{}
 			}
@@ -356,6 +356,9 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 				}
 			}
 		case fieldAge:
+			if len(ss) < 2 {
+				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
+			}
 			// Age integer is in seconds
 			d, err := time.ParseDuration(ss[1] + "s")
 			if err != nil {
@@ -364,6 +367,9 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 
 			stats.Age = d
 		case fieldBytes:
+			if len(ss) < 2 {
+				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
+			}
 			bstats, err := parseNFSBytesStats(ss[1:])
 			if err != nil {
 				return nil, err
@@ -371,6 +377,9 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 
 			stats.Bytes = *bstats
 		case fieldEvents:
+			if len(ss) < 2 {
+				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
+			}
 			estats, err := parseNFSEventsStats(ss[1:])
 			if err != nil {
 				return nil, err
diff --git a/net_ip_socket.go b/net_ip_socket.go
new file mode 100644
index 0000000..bb9e966
--- /dev/null
+++ b/net_ip_socket.go
@@ -0,0 +1,228 @@
+// 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"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"strconv"
+	"strings"
+)
+
+const (
+	// readLimit is used by io.LimitReader while reading the content of the
+	// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
+	// as each line represents a single used socket.
+	// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
+	// With e.g. 150 Byte per line and the maximum number of 65535,
+	// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
+	readLimit = 4294967296 // Byte -> 4 GiB
+)
+
+// this contains generic data structures for both udp and tcp sockets
+type (
+	// NetIPSocket represents the contents of /proc/net/{t,u}dp{,6} file without the header.
+	NetIPSocket []*netIPSocketLine
+
+	// NetIPSocketSummary provides already computed values like the total queue lengths or
+	// the total number of used sockets. In contrast to NetIPSocket it does not collect
+	// the parsed lines into a slice.
+	NetIPSocketSummary struct {
+		// TxQueueLength shows the total queue length of all parsed tx_queue lengths.
+		TxQueueLength uint64
+		// RxQueueLength shows the total queue length of all parsed rx_queue lengths.
+		RxQueueLength uint64
+		// UsedSockets shows the total number of parsed lines representing the
+		// number of used sockets.
+		UsedSockets uint64
+	}
+
+	// netIPSocketLine represents the fields parsed from a single line
+	// in /proc/net/{t,u}dp{,6}. Fields which are not used by IPSocket are skipped.
+	// For the proc file format details, see https://linux.die.net/man/5/proc.
+	netIPSocketLine struct {
+		Sl        uint64
+		LocalAddr net.IP
+		LocalPort uint64
+		RemAddr   net.IP
+		RemPort   uint64
+		St        uint64
+		TxQueue   uint64
+		RxQueue   uint64
+		UID       uint64
+	}
+)
+
+func newNetIPSocket(file string) (NetIPSocket, error) {
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var netIPSocket NetIPSocket
+
+	lr := io.LimitReader(f, readLimit)
+	s := bufio.NewScanner(lr)
+	s.Scan() // skip first line with headers
+	for s.Scan() {
+		fields := strings.Fields(s.Text())
+		line, err := parseNetIPSocketLine(fields)
+		if err != nil {
+			return nil, err
+		}
+		netIPSocket = append(netIPSocket, line)
+	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
+	return netIPSocket, nil
+}
+
+// newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file.
+func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var netIPSocketSummary NetIPSocketSummary
+
+	lr := io.LimitReader(f, readLimit)
+	s := bufio.NewScanner(lr)
+	s.Scan() // skip first line with headers
+	for s.Scan() {
+		fields := strings.Fields(s.Text())
+		line, err := parseNetIPSocketLine(fields)
+		if err != nil {
+			return nil, err
+		}
+		netIPSocketSummary.TxQueueLength += line.TxQueue
+		netIPSocketSummary.RxQueueLength += line.RxQueue
+		netIPSocketSummary.UsedSockets++
+	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
+	return &netIPSocketSummary, nil
+}
+
+// the /proc/net/{t,u}dp{,6} files are network byte order for ipv4 and for ipv6 the address is four words consisting of four bytes each. In each of those four words the four bytes are written in reverse order.
+
+func parseIP(hexIP string) (net.IP, error) {
+	var byteIP []byte
+	byteIP, err := hex.DecodeString(hexIP)
+	if err != nil {
+		return nil, fmt.Errorf(
+			"cannot parse address field in socket line: %s", hexIP)
+	}
+	switch len(byteIP) {
+	case 4:
+		return net.IP{byteIP[3], byteIP[2], byteIP[1], byteIP[0]}, nil
+	case 16:
+		i := net.IP{
+			byteIP[3], byteIP[2], byteIP[1], byteIP[0],
+			byteIP[7], byteIP[6], byteIP[5], byteIP[4],
+			byteIP[11], byteIP[10], byteIP[9], byteIP[8],
+			byteIP[15], byteIP[14], byteIP[13], byteIP[12],
+		}
+		return i, nil
+	default:
+		return nil, fmt.Errorf("Unable to parse IP %s", hexIP)
+	}
+}
+
+// parseNetIPSocketLine parses a single line, represented by a list of fields.
+func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
+	line := &netIPSocketLine{}
+	if len(fields) < 8 {
+		return nil, fmt.Errorf(
+			"cannot parse net socket line as it has less then 8 columns: %s",
+			strings.Join(fields, " "),
+		)
+	}
+	var err error // parse error
+
+	// sl
+	s := strings.Split(fields[0], ":")
+	if len(s) != 2 {
+		return nil, fmt.Errorf(
+			"cannot parse sl field in socket line: %s", fields[0])
+	}
+
+	if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
+		return nil, fmt.Errorf("cannot parse sl value in socket line: %s", err)
+	}
+	// local_address
+	l := strings.Split(fields[1], ":")
+	if len(l) != 2 {
+		return nil, fmt.Errorf(
+			"cannot parse local_address field in socket line: %s", fields[1])
+	}
+	if line.LocalAddr, err = parseIP(l[0]); err != nil {
+		return nil, err
+	}
+	if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
+		return nil, fmt.Errorf(
+			"cannot parse local_address port value in socket line: %s", err)
+	}
+
+	// remote_address
+	r := strings.Split(fields[2], ":")
+	if len(r) != 2 {
+		return nil, fmt.Errorf(
+			"cannot parse rem_address field in socket line: %s", fields[1])
+	}
+	if line.RemAddr, err = parseIP(r[0]); err != nil {
+		return nil, err
+	}
+	if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
+		return nil, fmt.Errorf(
+			"cannot parse rem_address port value in socket line: %s", err)
+	}
+
+	// st
+	if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
+		return nil, fmt.Errorf(
+			"cannot parse st value in socket line: %s", err)
+	}
+
+	// tx_queue and rx_queue
+	q := strings.Split(fields[4], ":")
+	if len(q) != 2 {
+		return nil, fmt.Errorf(
+			"cannot parse tx/rx queues in socket line as it has a missing colon: %s",
+			fields[4],
+		)
+	}
+	if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
+		return nil, fmt.Errorf("cannot parse tx_queue value in socket line: %s", err)
+	}
+	if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
+		return nil, fmt.Errorf("cannot parse rx_queue value in socket line: %s", err)
+	}
+
+	// uid
+	if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
+		return nil, fmt.Errorf(
+			"cannot parse uid value in socket line: %s", err)
+	}
+
+	return line, nil
+}
diff --git a/net_protocols.go b/net_protocols.go
new file mode 100644
index 0000000..8c6de37
--- /dev/null
+++ b/net_protocols.go
@@ -0,0 +1,180 @@
+// 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"
+)
+
+// NetProtocolStats stores the contents from /proc/net/protocols
+type NetProtocolStats map[string]NetProtocolStatLine
+
+// NetProtocolStatLine contains a single line parsed from /proc/net/protocols. We
+// only care about the first six columns as the rest are not likely to change
+// and only serve to provide a set of capabilities for each protocol.
+type NetProtocolStatLine struct {
+	Name         string // 0 The name of the protocol
+	Size         uint64 // 1 The size, in bytes, of a given protocol structure. e.g. sizeof(struct tcp_sock) or sizeof(struct unix_sock)
+	Sockets      int64  // 2 Number of sockets in use by this protocol
+	Memory       int64  // 3 Number of 4KB pages allocated by all sockets of this protocol
+	Pressure     int    // 4 This is either yes, no, or NI (not implemented). For the sake of simplicity we treat NI as not experiencing memory pressure.
+	MaxHeader    uint64 // 5 Protocol specific max header size
+	Slab         bool   // 6 Indicates whether or not memory is allocated from the SLAB
+	ModuleName   string // 7 The name of the module that implemented this protocol or "kernel" if not from a module
+	Capabilities NetProtocolCapabilities
+}
+
+// NetProtocolCapabilities contains a list of capabilities for each protocol
+type NetProtocolCapabilities struct {
+	Close               bool // 8
+	Connect             bool // 9
+	Disconnect          bool // 10
+	Accept              bool // 11
+	IoCtl               bool // 12
+	Init                bool // 13
+	Destroy             bool // 14
+	Shutdown            bool // 15
+	SetSockOpt          bool // 16
+	GetSockOpt          bool // 17
+	SendMsg             bool // 18
+	RecvMsg             bool // 19
+	SendPage            bool // 20
+	Bind                bool // 21
+	BacklogRcv          bool // 22
+	Hash                bool // 23
+	UnHash              bool // 24
+	GetPort             bool // 25
+	EnterMemoryPressure bool // 26
+}
+
+// NetProtocols reads stats from /proc/net/protocols and returns a map of
+// PortocolStatLine entries. As of this writing no official Linux Documentation
+// exists, however the source is fairly self-explanatory and the format seems
+// stable since its introduction in 2.6.12-rc2
+// Linux 2.6.12-rc2 - https://elixir.bootlin.com/linux/v2.6.12-rc2/source/net/core/sock.c#L1452
+// Linux 5.10 - https://elixir.bootlin.com/linux/v5.10.4/source/net/core/sock.c#L3586
+func (fs FS) NetProtocols() (NetProtocolStats, error) {
+	data, err := util.ReadFileNoStat(fs.proc.Path("net/protocols"))
+	if err != nil {
+		return NetProtocolStats{}, err
+	}
+	return parseNetProtocols(bufio.NewScanner(bytes.NewReader(data)))
+}
+
+func parseNetProtocols(s *bufio.Scanner) (NetProtocolStats, error) {
+	nps := NetProtocolStats{}
+
+	// Skip the header line
+	s.Scan()
+
+	for s.Scan() {
+		line, err := nps.parseLine(s.Text())
+		if err != nil {
+			return NetProtocolStats{}, err
+		}
+
+		nps[line.Name] = *line
+	}
+	return nps, nil
+}
+
+func (ps NetProtocolStats) parseLine(rawLine string) (*NetProtocolStatLine, error) {
+	line := &NetProtocolStatLine{Capabilities: NetProtocolCapabilities{}}
+	var err error
+	const enabled = "yes"
+	const disabled = "no"
+
+	fields := strings.Fields(rawLine)
+	line.Name = fields[0]
+	line.Size, err = strconv.ParseUint(fields[1], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	line.Sockets, err = strconv.ParseInt(fields[2], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	line.Memory, err = strconv.ParseInt(fields[3], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	if fields[4] == enabled {
+		line.Pressure = 1
+	} else if fields[4] == disabled {
+		line.Pressure = 0
+	} else {
+		line.Pressure = -1
+	}
+	line.MaxHeader, err = strconv.ParseUint(fields[5], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	if fields[6] == enabled {
+		line.Slab = true
+	} else if fields[6] == disabled {
+		line.Slab = false
+	} else {
+		return nil, fmt.Errorf("unable to parse capability for protocol: %s", line.Name)
+	}
+	line.ModuleName = fields[7]
+
+	err = line.Capabilities.parseCapabilities(fields[8:])
+	if err != nil {
+		return nil, err
+	}
+
+	return line, nil
+}
+
+func (pc *NetProtocolCapabilities) parseCapabilities(capabilities []string) error {
+	// The capabilities are all bools so we can loop over to map them
+	capabilityFields := [...]*bool{
+		&pc.Close,
+		&pc.Connect,
+		&pc.Disconnect,
+		&pc.Accept,
+		&pc.IoCtl,
+		&pc.Init,
+		&pc.Destroy,
+		&pc.Shutdown,
+		&pc.SetSockOpt,
+		&pc.GetSockOpt,
+		&pc.SendMsg,
+		&pc.RecvMsg,
+		&pc.SendPage,
+		&pc.Bind,
+		&pc.BacklogRcv,
+		&pc.Hash,
+		&pc.UnHash,
+		&pc.GetPort,
+		&pc.EnterMemoryPressure,
+	}
+
+	for i := 0; i < len(capabilities); i++ {
+		if capabilities[i] == "y" {
+			*capabilityFields[i] = true
+		} else if capabilities[i] == "n" {
+			*capabilityFields[i] = false
+		} else {
+			return fmt.Errorf("unable to parse capability block for protocol: position %d", i)
+		}
+	}
+	return nil
+}
diff --git a/net_tcp.go b/net_tcp.go
new file mode 100644
index 0000000..5277629
--- /dev/null
+++ b/net_tcp.go
@@ -0,0 +1,64 @@
+// 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
+
+type (
+	// NetTCP represents the contents of /proc/net/tcp{,6} file without the header.
+	NetTCP []*netIPSocketLine
+
+	// NetTCPSummary provides already computed values like the total queue lengths or
+	// the total number of used sockets. In contrast to NetTCP it does not collect
+	// the parsed lines into a slice.
+	NetTCPSummary NetIPSocketSummary
+)
+
+// NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams
+// read from /proc/net/tcp.
+func (fs FS) NetTCP() (NetTCP, error) {
+	return newNetTCP(fs.proc.Path("net/tcp"))
+}
+
+// NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams
+// read from /proc/net/tcp6.
+func (fs FS) NetTCP6() (NetTCP, error) {
+	return newNetTCP(fs.proc.Path("net/tcp6"))
+}
+
+// NetTCPSummary returns already computed statistics like the total queue lengths
+// for TCP datagrams read from /proc/net/tcp.
+func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
+	return newNetTCPSummary(fs.proc.Path("net/tcp"))
+}
+
+// NetTCP6Summary returns already computed statistics like the total queue lengths
+// for TCP datagrams read from /proc/net/tcp6.
+func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) {
+	return newNetTCPSummary(fs.proc.Path("net/tcp6"))
+}
+
+// newNetTCP creates a new NetTCP{,6} from the contents of the given file.
+func newNetTCP(file string) (NetTCP, error) {
+	n, err := newNetIPSocket(file)
+	n1 := NetTCP(n)
+	return n1, err
+}
+
+func newNetTCPSummary(file string) (*NetTCPSummary, error) {
+	n, err := newNetIPSocketSummary(file)
+	if n == nil {
+		return nil, err
+	}
+	n1 := NetTCPSummary(*n)
+	return &n1, err
+}
diff --git a/net_udp.go b/net_udp.go
index d017e3f..9ac3daf 100644
--- a/net_udp.go
+++ b/net_udp.go
@@ -13,58 +13,14 @@
 
 package procfs
 
-import (
-	"bufio"
-	"encoding/hex"
-	"fmt"
-	"io"
-	"net"
-	"os"
-	"strconv"
-	"strings"
-)
-
-const (
-	// readLimit is used by io.LimitReader while reading the content of the
-	// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
-	// as each line represents a single used socket.
-	// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
-	// With e.g. 150 Byte per line and the maximum number of 65535,
-	// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
-	readLimit = 4294967296 // Byte -> 4 GiB
-)
-
 type (
 	// NetUDP represents the contents of /proc/net/udp{,6} file without the header.
-	NetUDP []*netUDPLine
+	NetUDP []*netIPSocketLine
 
 	// NetUDPSummary provides already computed values like the total queue lengths or
 	// the total number of used sockets. In contrast to NetUDP it does not collect
 	// the parsed lines into a slice.
-	NetUDPSummary struct {
-		// TxQueueLength shows the total queue length of all parsed tx_queue lengths.
-		TxQueueLength uint64
-		// RxQueueLength shows the total queue length of all parsed rx_queue lengths.
-		RxQueueLength uint64
-		// UsedSockets shows the total number of parsed lines representing the
-		// number of used sockets.
-		UsedSockets uint64
-	}
-
-	// netUDPLine represents the fields parsed from a single line
-	// in /proc/net/udp{,6}. Fields which are not used by UDP are skipped.
-	// For the proc file format details, see https://linux.die.net/man/5/proc.
-	netUDPLine struct {
-		Sl        uint64
-		LocalAddr net.IP
-		LocalPort uint64
-		RemAddr   net.IP
-		RemPort   uint64
-		St        uint64
-		TxQueue   uint64
-		RxQueue   uint64
-		UID       uint64
-	}
+	NetUDPSummary NetIPSocketSummary
 )
 
 // NetUDP returns the IPv4 kernel/networking statistics for UDP datagrams
@@ -93,137 +49,16 @@ func (fs FS) NetUDP6Summary() (*NetUDPSummary, error) {
 
 // newNetUDP creates a new NetUDP{,6} from the contents of the given file.
 func newNetUDP(file string) (NetUDP, error) {
-	f, err := os.Open(file)
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close()
-
-	netUDP := NetUDP{}
-
-	lr := io.LimitReader(f, readLimit)
-	s := bufio.NewScanner(lr)
-	s.Scan() // skip first line with headers
-	for s.Scan() {
-		fields := strings.Fields(s.Text())
-		line, err := parseNetUDPLine(fields)
-		if err != nil {
-			return nil, err
-		}
-		netUDP = append(netUDP, line)
-	}
-	if err := s.Err(); err != nil {
-		return nil, err
-	}
-	return netUDP, nil
+	n, err := newNetIPSocket(file)
+	n1 := NetUDP(n)
+	return n1, err
 }
 
-// newNetUDPSummary creates a new NetUDP{,6} from the contents of the given file.
 func newNetUDPSummary(file string) (*NetUDPSummary, error) {
-	f, err := os.Open(file)
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close()
-
-	netUDPSummary := &NetUDPSummary{}
-
-	lr := io.LimitReader(f, readLimit)
-	s := bufio.NewScanner(lr)
-	s.Scan() // skip first line with headers
-	for s.Scan() {
-		fields := strings.Fields(s.Text())
-		line, err := parseNetUDPLine(fields)
-		if err != nil {
-			return nil, err
-		}
-		netUDPSummary.TxQueueLength += line.TxQueue
-		netUDPSummary.RxQueueLength += line.RxQueue
-		netUDPSummary.UsedSockets++
-	}
-	if err := s.Err(); err != nil {
+	n, err := newNetIPSocketSummary(file)
+	if n == nil {
 		return nil, err
 	}
-	return netUDPSummary, nil
-}
-
-// parseNetUDPLine parses a single line, represented by a list of fields.
-func parseNetUDPLine(fields []string) (*netUDPLine, error) {
-	line := &netUDPLine{}
-	if len(fields) < 8 {
-		return nil, fmt.Errorf(
-			"cannot parse net udp socket line as it has less then 8 columns: %s",
-			strings.Join(fields, " "),
-		)
-	}
-	var err error // parse error
-
-	// sl
-	s := strings.Split(fields[0], ":")
-	if len(s) != 2 {
-		return nil, fmt.Errorf(
-			"cannot parse sl field in udp socket line: %s", fields[0])
-	}
-
-	if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
-		return nil, fmt.Errorf("cannot parse sl value in udp socket line: %s", err)
-	}
-	// local_address
-	l := strings.Split(fields[1], ":")
-	if len(l) != 2 {
-		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 {
-		return nil, fmt.Errorf(
-			"cannot parse local_address value in udp socket line: %s", err)
-	}
-	if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
-		return nil, fmt.Errorf(
-			"cannot parse local_address port value in udp socket line: %s", err)
-	}
-
-	// remote_address
-	r := strings.Split(fields[2], ":")
-	if len(r) != 2 {
-		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 {
-		return nil, fmt.Errorf(
-			"cannot parse rem_address value in udp socket line: %s", err)
-	}
-	if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
-		return nil, fmt.Errorf(
-			"cannot parse rem_address port value in udp socket line: %s", err)
-	}
-
-	// st
-	if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
-		return nil, fmt.Errorf(
-			"cannot parse st value in udp socket line: %s", err)
-	}
-
-	// tx_queue and rx_queue
-	q := strings.Split(fields[4], ":")
-	if len(q) != 2 {
-		return nil, fmt.Errorf(
-			"cannot parse tx/rx queues in udp socket line as it has a missing colon: %s",
-			fields[4],
-		)
-	}
-	if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
-		return nil, fmt.Errorf("cannot parse tx_queue value in udp socket line: %s", err)
-	}
-	if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
-		return nil, fmt.Errorf("cannot parse rx_queue value in udp socket line: %s", err)
-	}
-
-	// uid
-	if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
-		return nil, fmt.Errorf(
-			"cannot parse uid value in udp socket line: %s", err)
-	}
-
-	return line, nil
+	n1 := NetUDPSummary(*n)
+	return &n1, err
 }
diff --git a/proc_cgroup.go b/proc_cgroup.go
index 4abd464..0094a13 100644
--- a/proc_cgroup.go
+++ b/proc_cgroup.go
@@ -49,7 +49,7 @@ type Cgroup struct {
 func parseCgroupString(cgroupStr string) (*Cgroup, error) {
 	var err error
 
-	fields := strings.Split(cgroupStr, ":")
+	fields := strings.SplitN(cgroupStr, ":", 3)
 	if len(fields) < 3 {
 		return nil, fmt.Errorf("at least 3 fields required, found %d fields in cgroup string: %s", len(fields), cgroupStr)
 	}
diff --git a/proc_limits.go b/proc_limits.go
index 91ee24d..f28ab9e 100644
--- a/proc_limits.go
+++ b/proc_limits.go
@@ -26,55 +26,55 @@ import (
 // http://man7.org/linux/man-pages/man2/getrlimit.2.html.
 type ProcLimits struct {
 	// CPU time limit in seconds.
-	CPUTime int64
+	CPUTime uint64
 	// Maximum size of files that the process may create.
-	FileSize int64
+	FileSize uint64
 	// Maximum size of the process's data segment (initialized data,
 	// uninitialized data, and heap).
-	DataSize int64
+	DataSize uint64
 	// Maximum size of the process stack in bytes.
-	StackSize int64
+	StackSize uint64
 	// Maximum size of a core file.
-	CoreFileSize int64
+	CoreFileSize uint64
 	// Limit of the process's resident set in pages.
-	ResidentSet int64
+	ResidentSet uint64
 	// Maximum number of processes that can be created for the real user ID of
 	// the calling process.
-	Processes int64
+	Processes uint64
 	// Value one greater than the maximum file descriptor number that can be
 	// opened by this process.
-	OpenFiles int64
+	OpenFiles uint64
 	// Maximum number of bytes of memory that may be locked into RAM.
-	LockedMemory int64
+	LockedMemory uint64
 	// Maximum size of the process's virtual memory address space in bytes.
-	AddressSpace int64
+	AddressSpace uint64
 	// Limit on the combined number of flock(2) locks and fcntl(2) leases that
 	// this process may establish.
-	FileLocks int64
+	FileLocks uint64
 	// Limit of signals that may be queued for the real user ID of the calling
 	// process.
-	PendingSignals int64
+	PendingSignals uint64
 	// Limit on the number of bytes that can be allocated for POSIX message
 	// queues for the real user ID of the calling process.
-	MsqqueueSize int64
+	MsqqueueSize uint64
 	// Limit of the nice priority set using setpriority(2) or nice(2).
-	NicePriority int64
+	NicePriority uint64
 	// Limit of the real-time priority set using sched_setscheduler(2) or
 	// sched_setparam(2).
-	RealtimePriority int64
+	RealtimePriority uint64
 	// Limit (in microseconds) on the amount of CPU time that a process
 	// scheduled under a real-time scheduling policy may consume without making
 	// a blocking system call.
-	RealtimeTimeout int64
+	RealtimeTimeout uint64
 }
 
 const (
-	limitsFields    = 3
+	limitsFields    = 4
 	limitsUnlimited = "unlimited"
 )
 
 var (
-	limitsDelimiter = regexp.MustCompile("  +")
+	limitsMatch = regexp.MustCompile(`(Max \w+\s{0,1}?\w*\s{0,1}\w*)\s{2,}(\w+)\s+(\w+)`)
 )
 
 // NewLimits returns the current soft limits of the process.
@@ -96,46 +96,50 @@ func (p Proc) Limits() (ProcLimits, error) {
 		l = ProcLimits{}
 		s = bufio.NewScanner(f)
 	)
+
+	s.Scan() // Skip limits header
+
 	for s.Scan() {
-		fields := limitsDelimiter.Split(s.Text(), limitsFields)
+		//fields := limitsMatch.Split(s.Text(), limitsFields)
+		fields := limitsMatch.FindStringSubmatch(s.Text())
 		if len(fields) != limitsFields {
 			return ProcLimits{}, fmt.Errorf(
 				"couldn't parse %s line %s", f.Name(), s.Text())
 		}
 
-		switch fields[0] {
+		switch fields[1] {
 		case "Max cpu time":
-			l.CPUTime, err = parseInt(fields[1])
+			l.CPUTime, err = parseUint(fields[2])
 		case "Max file size":
-			l.FileSize, err = parseInt(fields[1])
+			l.FileSize, err = parseUint(fields[2])
 		case "Max data size":
-			l.DataSize, err = parseInt(fields[1])
+			l.DataSize, err = parseUint(fields[2])
 		case "Max stack size":
-			l.StackSize, err = parseInt(fields[1])
+			l.StackSize, err = parseUint(fields[2])
 		case "Max core file size":
-			l.CoreFileSize, err = parseInt(fields[1])
+			l.CoreFileSize, err = parseUint(fields[2])
 		case "Max resident set":
-			l.ResidentSet, err = parseInt(fields[1])
+			l.ResidentSet, err = parseUint(fields[2])
 		case "Max processes":
-			l.Processes, err = parseInt(fields[1])
+			l.Processes, err = parseUint(fields[2])
 		case "Max open files":
-			l.OpenFiles, err = parseInt(fields[1])
+			l.OpenFiles, err = parseUint(fields[2])
 		case "Max locked memory":
-			l.LockedMemory, err = parseInt(fields[1])
+			l.LockedMemory, err = parseUint(fields[2])
 		case "Max address space":
-			l.AddressSpace, err = parseInt(fields[1])
+			l.AddressSpace, err = parseUint(fields[2])
 		case "Max file locks":
-			l.FileLocks, err = parseInt(fields[1])
+			l.FileLocks, err = parseUint(fields[2])
 		case "Max pending signals":
-			l.PendingSignals, err = parseInt(fields[1])
+			l.PendingSignals, err = parseUint(fields[2])
 		case "Max msgqueue size":
-			l.MsqqueueSize, err = parseInt(fields[1])
+			l.MsqqueueSize, err = parseUint(fields[2])
 		case "Max nice priority":
-			l.NicePriority, err = parseInt(fields[1])
+			l.NicePriority, err = parseUint(fields[2])
 		case "Max realtime priority":
-			l.RealtimePriority, err = parseInt(fields[1])
+			l.RealtimePriority, err = parseUint(fields[2])
 		case "Max realtime timeout":
-			l.RealtimeTimeout, err = parseInt(fields[1])
+			l.RealtimeTimeout, err = parseUint(fields[2])
 		}
 		if err != nil {
 			return ProcLimits{}, err
@@ -145,11 +149,11 @@ func (p Proc) Limits() (ProcLimits, error) {
 	return l, s.Err()
 }
 
-func parseInt(s string) (int64, error) {
+func parseUint(s string) (uint64, error) {
 	if s == limitsUnlimited {
-		return -1, nil
+		return 18446744073709551615, nil
 	}
-	i, err := strconv.ParseInt(s, 10, 64)
+	i, err := strconv.ParseUint(s, 10, 64)
 	if err != nil {
 		return 0, fmt.Errorf("couldn't parse value %s: %s", s, err)
 	}
diff --git a/slab.go b/slab.go
new file mode 100644
index 0000000..c999500
--- /dev/null
+++ b/slab.go
@@ -0,0 +1,151 @@
+// 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"
+	"errors"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+var (
+	slabSpace  = regexp.MustCompile(`\s+`)
+	slabVer    = regexp.MustCompile(`slabinfo -`)
+	slabHeader = regexp.MustCompile(`# name`)
+)
+
+// Slab represents a slab pool in the kernel.
+type Slab struct {
+	Name         string
+	ObjActive    int64
+	ObjNum       int64
+	ObjSize      int64
+	ObjPerSlab   int64
+	PagesPerSlab int64
+	// tunables
+	Limit        int64
+	Batch        int64
+	SharedFactor int64
+	SlabActive   int64
+	SlabNum      int64
+	SharedAvail  int64
+}
+
+// SlabInfo represents info for all slabs.
+type SlabInfo struct {
+	Slabs []*Slab
+}
+
+func shouldParseSlab(line string) bool {
+	if slabVer.MatchString(line) {
+		return false
+	}
+	if slabHeader.MatchString(line) {
+		return false
+	}
+	return true
+}
+
+// parseV21SlabEntry is used to parse a line from /proc/slabinfo version 2.1.
+func parseV21SlabEntry(line string) (*Slab, error) {
+	// First cleanup whitespace.
+	l := slabSpace.ReplaceAllString(line, " ")
+	s := strings.Split(l, " ")
+	if len(s) != 16 {
+		return nil, errors.New("unable to parse: " + line)
+	}
+	var err error
+	i := &Slab{Name: s[0]}
+	i.ObjActive, err = strconv.ParseInt(s[1], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.ObjNum, err = strconv.ParseInt(s[2], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.ObjSize, err = strconv.ParseInt(s[3], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.ObjPerSlab, err = strconv.ParseInt(s[4], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.PagesPerSlab, err = strconv.ParseInt(s[5], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.Limit, err = strconv.ParseInt(s[8], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.Batch, err = strconv.ParseInt(s[9], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.SharedFactor, err = strconv.ParseInt(s[10], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.SlabActive, err = strconv.ParseInt(s[13], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.SlabNum, err = strconv.ParseInt(s[14], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	i.SharedAvail, err = strconv.ParseInt(s[15], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	return i, nil
+}
+
+// parseSlabInfo21 is used to parse a slabinfo 2.1 file.
+func parseSlabInfo21(r *bytes.Reader) (SlabInfo, error) {
+	scanner := bufio.NewScanner(r)
+	s := SlabInfo{Slabs: []*Slab{}}
+	for scanner.Scan() {
+		line := scanner.Text()
+		if !shouldParseSlab(line) {
+			continue
+		}
+		slab, err := parseV21SlabEntry(line)
+		if err != nil {
+			return s, err
+		}
+		s.Slabs = append(s.Slabs, slab)
+	}
+	return s, nil
+}
+
+// SlabInfo reads data from /proc/slabinfo
+func (fs FS) SlabInfo() (SlabInfo, error) {
+	// TODO: Consider passing options to allow for parsing different
+	// slabinfo versions. However, slabinfo 2.1 has been stable since
+	// kernel 2.6.10 and later.
+	data, err := util.ReadFileNoStat(fs.proc.Path("slabinfo"))
+	if err != nil {
+		return SlabInfo{}, err
+	}
+
+	return parseSlabInfo21(bytes.NewReader(data))
+}
diff --git a/sysfs/vmstat_numa.go b/sysfs/vmstat_numa.go
new file mode 100644
index 0000000..d71cd4a
--- /dev/null
+++ b/sysfs/vmstat_numa.go
@@ -0,0 +1,246 @@
+// 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 sysfs
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+var (
+	nodePattern      = "devices/system/node/node[0-9]*"
+	nodeNumberRegexp = regexp.MustCompile(`.*devices/system/node/node([0-9]*)`)
+)
+
+type VMStat struct {
+	NrFreePages                uint64
+	NrZoneInactiveAnon         uint64
+	NrZoneActiveAnon           uint64
+	NrZoneInactiveFile         uint64
+	NrZoneActiveFile           uint64
+	NrZoneUnevictable          uint64
+	NrZoneWritePending         uint64
+	NrMlock                    uint64
+	NrPageTablePages           uint64
+	NrKernelStack              uint64
+	NrBounce                   uint64
+	NrZspages                  uint64
+	NrFreeCma                  uint64
+	NumaHit                    uint64
+	NumaMiss                   uint64
+	NumaForeign                uint64
+	NumaInterleave             uint64
+	NumaLocal                  uint64
+	NumaOther                  uint64
+	NrInactiveAnon             uint64
+	NrActiveAnon               uint64
+	NrInactiveFile             uint64
+	NrActiveFile               uint64
+	NrUnevictable              uint64
+	NrSlabReclaimable          uint64
+	NrSlabUnreclaimable        uint64
+	NrIsolatedAnon             uint64
+	NrIsolatedFile             uint64
+	WorkingsetNodes            uint64
+	WorkingsetRefault          uint64
+	WorkingsetActivate         uint64
+	WorkingsetRestore          uint64
+	WorkingsetNodereclaim      uint64
+	NrAnonPages                uint64
+	NrMapped                   uint64
+	NrFilePages                uint64
+	NrDirty                    uint64
+	NrWriteback                uint64
+	NrWritebackTemp            uint64
+	NrShmem                    uint64
+	NrShmemHugepages           uint64
+	NrShmemPmdmapped           uint64
+	NrFileHugepages            uint64
+	NrFilePmdmapped            uint64
+	NrAnonTransparentHugepages uint64
+	NrVmscanWrite              uint64
+	NrVmscanImmediateReclaim   uint64
+	NrDirtied                  uint64
+	NrWritten                  uint64
+	NrKernelMiscReclaimable    uint64
+	NrFollPinAcquired          uint64
+	NrFollPinReleased          uint64
+}
+
+func (fs FS) VMStatNUMA() (map[int]VMStat, error) {
+	m := make(map[int]VMStat)
+	nodes, err := filepath.Glob(fs.sys.Path(nodePattern))
+	if err != nil {
+		return nil, err
+	}
+
+	for _, node := range nodes {
+		nodeNumbers := nodeNumberRegexp.FindStringSubmatch(node)
+		if len(nodeNumbers) != 2 {
+			continue
+		}
+		nodeNumber, err := strconv.Atoi(nodeNumbers[1])
+		if err != nil {
+			return nil, err
+		}
+		file, err := util.ReadFileNoStat(filepath.Join(node, "vmstat"))
+		if err != nil {
+			return nil, err
+		}
+		nodeStats, err := parseVMStatNUMA(file)
+		if err != nil {
+			return nil, err
+		}
+		m[nodeNumber] = nodeStats
+	}
+	return m, nil
+}
+
+func parseVMStatNUMA(r []byte) (VMStat, error) {
+	var (
+		vmStat  = VMStat{}
+		scanner = bufio.NewScanner(bytes.NewReader(r))
+	)
+
+	for scanner.Scan() {
+		line := strings.TrimSpace(scanner.Text())
+		if line == "" {
+			continue
+		}
+		parts := strings.Fields(line)
+		if len(parts) != 2 {
+			return vmStat, fmt.Errorf("line scan did not return 2 fields: %s", line)
+		}
+
+		fv, err := strconv.ParseUint(parts[1], 10, 64)
+		if err != nil {
+			return vmStat, fmt.Errorf("invalid value in vmstat: %w", err)
+		}
+		switch parts[0] {
+		case "nr_free_pages":
+			vmStat.NrFreePages = fv
+		case "nr_zone_inactive_anon":
+			vmStat.NrZoneInactiveAnon = fv
+		case "nr_zone_active_anon":
+			vmStat.NrZoneActiveAnon = fv
+		case "nr_zone_inactive_file":
+			vmStat.NrZoneActiveFile = fv
+		case "nr_zone_active_file":
+			vmStat.NrZoneActiveFile = fv
+		case "nr_zone_unevictable":
+			vmStat.NrZoneUnevictable = fv
+		case "nr_zone_write_pending":
+			vmStat.NrZoneWritePending = fv
+		case "nr_mlock":
+			vmStat.NrMlock = fv
+		case "nr_page_table_pages":
+			vmStat.NrPageTablePages = fv
+		case "nr_kernel_stack":
+			vmStat.NrKernelStack = fv
+		case "nr_bounce":
+			vmStat.NrBounce = fv
+		case "nr_zspages":
+			vmStat.NrZspages = fv
+		case "nr_free_cma":
+			vmStat.NrFreeCma = fv
+		case "numa_hit":
+			vmStat.NumaHit = fv
+		case "numa_miss":
+			vmStat.NumaMiss = fv
+		case "numa_foreign":
+			vmStat.NumaForeign = fv
+		case "numa_interleave":
+			vmStat.NumaInterleave = fv
+		case "numa_local":
+			vmStat.NumaLocal = fv
+		case "numa_other":
+			vmStat.NumaOther = fv
+		case "nr_inactive_anon":
+			vmStat.NrInactiveAnon = fv
+		case "nr_active_anon":
+			vmStat.NrActiveAnon = fv
+		case "nr_inactive_file":
+			vmStat.NrInactiveFile = fv
+		case "nr_active_file":
+			vmStat.NrActiveFile = fv
+		case "nr_unevictable":
+			vmStat.NrUnevictable = fv
+		case "nr_slab_reclaimable":
+			vmStat.NrSlabReclaimable = fv
+		case "nr_slab_unreclaimable":
+			vmStat.NrSlabUnreclaimable = fv
+		case "nr_isolated_anon":
+			vmStat.NrIsolatedAnon = fv
+		case "nr_isolated_file":
+			vmStat.NrIsolatedFile = fv
+		case "workingset_nodes":
+			vmStat.WorkingsetNodes = fv
+		case "workingset_refault":
+			vmStat.WorkingsetRefault = fv
+		case "workingset_activate":
+			vmStat.WorkingsetActivate = fv
+		case "workingset_restore":
+			vmStat.WorkingsetRestore = fv
+		case "workingset_nodereclaim":
+			vmStat.WorkingsetNodereclaim = fv
+		case "nr_anon_pages":
+			vmStat.NrAnonPages = fv
+		case "nr_mapped":
+			vmStat.NrMapped = fv
+		case "nr_file_pages":
+			vmStat.NrFilePages = fv
+		case "nr_dirty":
+			vmStat.NrDirty = fv
+		case "nr_writeback":
+			vmStat.NrWriteback = fv
+		case "nr_writeback_temp":
+			vmStat.NrWritebackTemp = fv
+		case "nr_shmem":
+			vmStat.NrShmem = fv
+		case "nr_shmem_hugepages":
+			vmStat.NrShmemHugepages = fv
+		case "nr_shmem_pmdmapped":
+			vmStat.NrShmemPmdmapped = fv
+		case "nr_file_hugepages":
+			vmStat.NrFileHugepages = fv
+		case "nr_file_pmdmapped":
+			vmStat.NrFilePmdmapped = fv
+		case "nr_anon_transparent_hugepages":
+			vmStat.NrAnonTransparentHugepages = fv
+		case "nr_vmscan_write":
+			vmStat.NrVmscanWrite = fv
+		case "nr_vmscan_immediate_reclaim":
+			vmStat.NrVmscanImmediateReclaim = fv
+		case "nr_dirtied":
+			vmStat.NrDirtied = fv
+		case "nr_written":
+			vmStat.NrWritten = fv
+		case "nr_kernel_misc_reclaimable":
+			vmStat.NrKernelMiscReclaimable = fv
+		case "nr_foll_pin_acquired":
+			vmStat.NrFollPinAcquired = fv
+		case "nr_foll_pin_released":
+			vmStat.NrFollPinReleased = fv
+		}
+
+	}
+	return vmStat, scanner.Err()
+}
diff --git a/xfs/parse.go b/xfs/parse.go
index ba3afea..260501e 100644
--- a/xfs/parse.go
+++ b/xfs/parse.go
@@ -286,8 +286,8 @@ func readWriteStats(us []uint32) (ReadWriteStats, error) {
 	}
 
 	return ReadWriteStats{
-		Read:  us[0],
-		Write: us[1],
+		Write: us[0],
+		Read:  us[1],
 	}, nil
 }
 
diff --git a/xfs/xfs.go b/xfs/xfs.go
index cc8b36b..79bba65 100644
--- a/xfs/xfs.go
+++ b/xfs/xfs.go
@@ -124,8 +124,8 @@ type LogOperationStats struct {
 // ReadWriteStats contains statistics regarding the number of read and write
 // system calls for XFS filesystems.
 type ReadWriteStats struct {
-	Read  uint32
 	Write uint32
+	Read  uint32
 }
 
 // AttributeOperationStats contains statistics regarding manipulation of


Test patch:
diff --git a/cpuinfo_test.go b/cpuinfo_test.go
index bfec15d..9a00d69 100644
--- a/cpuinfo_test.go
+++ b/cpuinfo_test.go
@@ -115,18 +115,34 @@ processor 2: version = FF,  identification = 2733E8,  machine = 2964
 processor 3: version = FF,  identification = 2733E8,  machine = 2964
 
 cpu number      : 0
+physical id     : 2
+core id         : 0
+siblings        : 8
+cpu cores       : 4
 cpu MHz dynamic : 5000
 cpu MHz static  : 5000
 
 cpu number      : 1
+physical id     : 2
+core id         : 0
+siblings        : 8
+cpu cores       : 4
 cpu MHz dynamic : 5000
 cpu MHz static  : 5000
 
 cpu number      : 2
+physical id     : 2
+core id         : 1
+siblings        : 8
+cpu cores       : 4
 cpu MHz dynamic : 5000
 cpu MHz static  : 5000
 
 cpu number      : 3
+physical id     : 2
+core id         : 1
+siblings        : 8
+cpu cores       : 4
 cpu MHz dynamic : 5000
 cpu MHz static  : 5000
 `
@@ -327,6 +343,18 @@ func TestCPUInfoParseS390X(t *testing.T) {
 	if want, have := 5000.0, cpuinfo[2].CPUMHz; want != have {
 		t.Errorf("want cpu MHz %v, have %v", want, have)
 	}
+	if want, have := uint(8), cpuinfo[3].Siblings; want != have {
+		t.Errorf("want siblings %v, have %v", want, have)
+	}
+	if want, have := "1", cpuinfo[3].CoreID; want != have {
+		t.Errorf("want core id %v, have %v", want, have)
+	}
+	if want, have := uint(4), cpuinfo[2].CPUCores; want != have {
+		t.Errorf("want cpu cores %v, have %v", want, have)
+	}
+	if want, have := "2", cpuinfo[2].PhysicalID; want != have {
+		t.Errorf("want physical id %v, have %v", want, have)
+	}
 }
 
 func TestCPUInfoParseMips(t *testing.T) {
diff --git a/mdstat_test.go b/mdstat_test.go
index 5404765..1b9a8ea 100644
--- a/mdstat_test.go
+++ b/mdstat_test.go
@@ -14,6 +14,7 @@
 package procfs
 
 import "testing"
+import "github.com/google/go-cmp/cmp"
 
 func TestFS_MDStat(t *testing.T) {
 	fs := getProcFixtures(t)
@@ -24,30 +25,30 @@ func TestFS_MDStat(t *testing.T) {
 	}
 
 	refs := map[string]MDStat{
-		"md127": {Name: "md127", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 312319552, BlocksSynced: 312319552},
-		"md0":   {Name: "md0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 248896, BlocksSynced: 248896},
-		"md4":   {Name: "md4", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 4883648, BlocksSynced: 4883648},
-		"md6":   {Name: "md6", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 195310144, BlocksSynced: 16775552},
-		"md3":   {Name: "md3", ActivityState: "active", DisksActive: 8, DisksTotal: 8, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 5853468288, BlocksSynced: 5853468288},
-		"md8":   {Name: "md8", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 195310144, BlocksSynced: 16775552},
-		"md7":   {Name: "md7", ActivityState: "active", DisksActive: 3, DisksTotal: 4, DisksFailed: 1, DisksSpare: 0, BlocksTotal: 7813735424, BlocksSynced: 7813735424},
-		"md9":   {Name: "md9", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, DisksSpare: 1, DisksFailed: 2, BlocksTotal: 523968, BlocksSynced: 0},
-		"md10":  {Name: "md10", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 314159265, BlocksSynced: 314159265},
-		"md11":  {Name: "md11", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 1, DisksSpare: 2, BlocksTotal: 4190208, BlocksSynced: 0},
-		"md12":  {Name: "md12", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksSpare: 0, DisksFailed: 0, BlocksTotal: 3886394368, BlocksSynced: 3886394368},
-		"md120": {Name: "md120", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 2095104, BlocksSynced: 2095104},
-		"md126": {Name: "md126", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1855870976, BlocksSynced: 1855870976},
-		"md219": {Name: "md219", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, DisksActive: 0, DisksSpare: 3, BlocksTotal: 7932, BlocksSynced: 7932},
-		"md00":  {Name: "md00", ActivityState: "active", DisksActive: 1, DisksTotal: 1, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 4186624, BlocksSynced: 4186624},
-		"md101": {Name: "md101", ActivityState: "active", DisksActive: 3, DisksTotal: 3, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 322560, BlocksSynced: 322560},
-		"md201": {Name: "md201", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1993728, BlocksSynced: 114176},
+		"md127": {Name: "md127", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 312319552, BlocksSynced: 312319552, Devices: []string{"sdi2", "sdj2"}},
+		"md0":   {Name: "md0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 248896, BlocksSynced: 248896, Devices: []string{"sdi1", "sdj1"}},
+		"md4":   {Name: "md4", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 4883648, BlocksSynced: 4883648, Devices: []string{"sda3", "sdb3"}},
+		"md6":   {Name: "md6", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 195310144, BlocksSynced: 16775552, Devices: []string{"sdb2", "sdc", "sda2"}},
+		"md3":   {Name: "md3", ActivityState: "active", DisksActive: 8, DisksTotal: 8, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 5853468288, BlocksSynced: 5853468288, Devices: []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}},
+		"md8":   {Name: "md8", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 195310144, BlocksSynced: 16775552, Devices: []string{"sdb1", "sda1", "sdc", "sde"}},
+		"md7":   {Name: "md7", ActivityState: "active", DisksActive: 3, DisksTotal: 4, DisksFailed: 1, DisksSpare: 0, BlocksTotal: 7813735424, BlocksSynced: 7813735424, Devices: []string{"sdb1", "sde1", "sdd1", "sdc1"}},
+		"md9":   {Name: "md9", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, DisksSpare: 1, DisksFailed: 2, BlocksTotal: 523968, BlocksSynced: 0, Devices: []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}},
+		"md10":  {Name: "md10", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 314159265, BlocksSynced: 314159265, Devices: []string{"sda1", "sdb1"}},
+		"md11":  {Name: "md11", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 1, DisksSpare: 2, BlocksTotal: 4190208, BlocksSynced: 0, Devices: []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}},
+		"md12":  {Name: "md12", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksSpare: 0, DisksFailed: 0, BlocksTotal: 3886394368, BlocksSynced: 3886394368, Devices: []string{"sdc2", "sdd2"}},
+		"md120": {Name: "md120", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 2095104, BlocksSynced: 2095104, Devices: []string{"sda1", "sdb1"}},
+		"md126": {Name: "md126", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1855870976, BlocksSynced: 1855870976, Devices: []string{"sdb", "sdc"}},
+		"md219": {Name: "md219", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, DisksActive: 0, DisksSpare: 3, BlocksTotal: 7932, BlocksSynced: 7932, Devices: []string{"sdc", "sda"}},
+		"md00":  {Name: "md00", ActivityState: "active", DisksActive: 1, DisksTotal: 1, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 4186624, BlocksSynced: 4186624, Devices: []string{"xvdb"}},
+		"md101": {Name: "md101", ActivityState: "active", DisksActive: 3, DisksTotal: 3, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 322560, BlocksSynced: 322560, Devices: []string{"sdb", "sdd", "sdc"}},
+		"md201": {Name: "md201", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1993728, BlocksSynced: 114176, Devices: []string{"sda3", "sdb3"}},
 	}
 
 	if want, have := len(refs), len(mdStats); want != have {
 		t.Errorf("want %d parsed md-devices, have %d", want, have)
 	}
 	for _, md := range mdStats {
-		if want, have := refs[md.Name], md; want != have {
+		if want, have := refs[md.Name], md; !cmp.Equal(want, have) {
 			t.Errorf("%s: want %v, have %v", md.Name, want, have)
 		}
 	}
diff --git a/meminfo_test.go b/meminfo_test.go
index 34b43bd..860f177 100644
--- a/meminfo_test.go
+++ b/meminfo_test.go
@@ -20,48 +20,48 @@ import (
 
 func TestMeminfo(t *testing.T) {
 	expected := Meminfo{
-		MemTotal:          15666184,
-		MemFree:           440324,
-		Buffers:           1020128,
-		Cached:            12007640,
-		SwapCached:        0,
-		Active:            6761276,
-		Inactive:          6532708,
-		ActiveAnon:        267256,
-		InactiveAnon:      268,
-		ActiveFile:        6494020,
-		InactiveFile:      6532440,
-		Unevictable:       0,
-		Mlocked:           0,
-		SwapTotal:         0,
-		SwapFree:          0,
-		Dirty:             768,
-		Writeback:         0,
-		AnonPages:         266216,
-		Mapped:            44204,
-		Shmem:             1308,
-		Slab:              1807264,
-		SReclaimable:      1738124,
-		SUnreclaim:        69140,
-		KernelStack:       1616,
-		PageTables:        5288,
-		NFSUnstable:       0,
-		Bounce:            0,
-		WritebackTmp:      0,
-		CommitLimit:       7833092,
-		CommittedAS:       530844,
-		VmallocTotal:      34359738367,
-		VmallocUsed:       36596,
-		VmallocChunk:      34359637840,
-		HardwareCorrupted: 0,
-		AnonHugePages:     12288,
-		HugePagesTotal:    0,
-		HugePagesFree:     0,
-		HugePagesRsvd:     0,
-		HugePagesSurp:     0,
-		Hugepagesize:      2048,
-		DirectMap4k:       91136,
-		DirectMap2M:       16039936,
+		MemTotal:          newuint64(15666184),
+		MemFree:           newuint64(440324),
+		Buffers:           newuint64(1020128),
+		Cached:            newuint64(12007640),
+		SwapCached:        newuint64(0),
+		Active:            newuint64(6761276),
+		Inactive:          newuint64(6532708),
+		ActiveAnon:        newuint64(267256),
+		InactiveAnon:      newuint64(268),
+		ActiveFile:        newuint64(6494020),
+		InactiveFile:      newuint64(6532440),
+		Unevictable:       newuint64(0),
+		Mlocked:           newuint64(0),
+		SwapTotal:         newuint64(0),
+		SwapFree:          newuint64(0),
+		Dirty:             newuint64(768),
+		Writeback:         newuint64(0),
+		AnonPages:         newuint64(266216),
+		Mapped:            newuint64(44204),
+		Shmem:             newuint64(1308),
+		Slab:              newuint64(1807264),
+		SReclaimable:      newuint64(1738124),
+		SUnreclaim:        newuint64(69140),
+		KernelStack:       newuint64(1616),
+		PageTables:        newuint64(5288),
+		NFSUnstable:       newuint64(0),
+		Bounce:            newuint64(0),
+		WritebackTmp:      newuint64(0),
+		CommitLimit:       newuint64(7833092),
+		CommittedAS:       newuint64(530844),
+		VmallocTotal:      newuint64(34359738367),
+		VmallocUsed:       newuint64(36596),
+		VmallocChunk:      newuint64(34359637840),
+		HardwareCorrupted: newuint64(0),
+		AnonHugePages:     newuint64(12288),
+		HugePagesTotal:    newuint64(0),
+		HugePagesFree:     newuint64(0),
+		HugePagesRsvd:     newuint64(0),
+		HugePagesSurp:     newuint64(0),
+		Hugepagesize:      newuint64(2048),
+		DirectMap4k:       newuint64(91136),
+		DirectMap2M:       newuint64(16039936),
 	}
 
 	have, err := getProcFixtures(t).Meminfo()
diff --git a/mountstats_test.go b/mountstats_test.go
index 146df96..391d50c 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -64,7 +64,7 @@ func TestMountStats(t *testing.T) {
 		},
 		{
 			name:    "NFSv4 device with too little info",
-			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nhello",
+			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nopts:",
 			invalid: true,
 		},
 		{
@@ -253,6 +253,26 @@ func TestMountStats(t *testing.T) {
 				},
 			}},
 		},
+		{
+			name: "NFS4.1 device with multiline impl_id OK",
+			s:    "device 192.168.0.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nopts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.0.2,fsc,local_lock=none\nage: 1234567\nimpl_id: name='FreeBSD 11.2-STABLE #0 r325575+c9231c7d6bd(HEAD): Mon Nov 18 22:46:47 UTC 2019\nuser@host:/path/to/something'\n,domain='something.org',date='1293840000,0'",
+			mounts: []*Mount{{
+				Device: "192.168.0.1:/srv",
+				Mount:  "/mnt/nfs",
+				Type:   "nfs4",
+				Stats: &MountStatsNFS{
+					StatVersion: "1.1",
+					Opts: map[string]string{"rw": "", "vers": "4.1",
+						"rsize": "131072", "wsize": "131072", "namlen": "255", "acregmin": "3",
+						"acregmax": "60", "acdirmin": "30", "acdirmax": "60", "fsc": "", "hard": "",
+						"proto": "tcp", "timeo": "600", "retrans": "2",
+						"sec": "sys", "clientaddr": "192.168.0.2",
+						"local_lock": "none",
+					},
+					Age: 1234567 * time.Second,
+				},
+			}},
+		},
 		{
 			name: "fixtures/proc OK",
 			mounts: []*Mount{
diff --git a/net_ip_socket_test.go b/net_ip_socket_test.go
new file mode 100644
index 0000000..a8bf91f
--- /dev/null
+++ b/net_ip_socket_test.go
@@ -0,0 +1,108 @@
+// 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 (
+	"net"
+	"reflect"
+	"testing"
+)
+
+func Test_parseNetIPSocketLine(t *testing.T) {
+	tests := []struct {
+		fields  []string
+		name    string
+		want    *netIPSocketLine
+		wantErr bool
+	}{
+		{
+			name:   "reading valid lines, no issue should happened",
+			fields: []string{"11:", "00000000:0000", "00000000:0000", "0A", "00000017:0000002A", "0:0", "0", "1000"},
+			want: &netIPSocketLine{
+				Sl:        11,
+				LocalAddr: net.IP{0, 0, 0, 0},
+				LocalPort: 0,
+				RemAddr:   net.IP{0, 0, 0, 0},
+				RemPort:   0,
+				St:        10,
+				TxQueue:   23,
+				RxQueue:   42,
+				UID:       1000,
+			},
+		},
+		{
+			name:    "error case - invalid line - number of fields/columns < 8",
+			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "0:0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse sl - not a valid uint",
+			fields:  []string{"a:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse local_address - not a valid hex",
+			fields:  []string{"1:", "0000000O:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse rem_address - not a valid hex",
+			fields:  []string{"1:", "00000000:0000", "0000000O:0000", "07", "00000000:00000001", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - cannot parse line - missing colon",
+			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse tx_queue - not a valid hex",
+			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "DEADCODE:00000001", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse rx_queue - not a valid hex",
+			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:FEEDCODE", "0:0", "0", "0"},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse UID - not a valid uint",
+			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "-10"},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := parseNetIPSocketLine(tt.fields)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("parseNetIPSocketLine() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if tt.want == nil && got != nil {
+				t.Errorf("parseNetIPSocketLine() = %v, want %v", got, tt.want)
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("parseNetIPSocketLine() = %#v, want %#v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/net_protocols_test.go b/net_protocols_test.go
new file mode 100644
index 0000000..cabbd93
--- /dev/null
+++ b/net_protocols_test.go
@@ -0,0 +1,83 @@
+// 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 (
+	"strings"
+	"testing"
+)
+
+func TestParseCapabilities(t *testing.T) {
+	rawStr := "y  y  y  y  y  y  y  y  y  y  y  y  y  n  y  y  y  y  y\n"
+	have := NetProtocolCapabilities{}
+	err := have.parseCapabilities(strings.Fields(rawStr))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := NetProtocolCapabilities{true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true}
+	if want != have {
+		t.Errorf("want %+v\nhave %+v\n", want, have)
+	}
+}
+
+func TestProtocolsParseLine(t *testing.T) {
+	rawStr := "TCP       1984  93064  1225378   no     320   yes  kernel      y  y  y  y  y  y  y  y  y  y  y  y  y  n  y  y  y  y  y\n"
+	protocols := NetProtocolStats{}
+	have, err := protocols.parseLine(rawStr)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := NetProtocolStatLine{"TCP", 1984, 93064, 1225378, 0, 320, true, "kernel", NetProtocolCapabilities{true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true}}
+	if want != *have {
+		t.Errorf("want %+v\nhave %+v\n", want, have)
+	}
+}
+
+func TestProtocolsParseProtocols(t *testing.T) {
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	protocolStats, err := fs.NetProtocols()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	lines := map[string]NetProtocolStatLine{
+		"PACKET":    {"PACKET", 1344, 2, -1, -1, 0, false, "kernel", NetProtocolCapabilities{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}},
+		"PINGv6":    {"PINGv6", 1112, 0, -1, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, false, true, false, false, true, true, true, true, false, true, true, true, true, true, false}},
+		"RAWv6":     {"RAWv6", 1112, 1, -1, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, false, true, true, true, true, false, false}},
+		"UDPLITEv6": {"UDPLITEv6", 1216, 0, 57, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, false, false, false, true, true, true, false}},
+		"UDPv6":     {"UDPv6", 1216, 10, 57, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, false, false, false, true, true, true, false}},
+		"TCPv6":     {"TCPv6", 2144, 1937, 1225378, 0, 320, true, "kernel", NetProtocolCapabilities{true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true}},
+		"UNIX":      {"UNIX", 1024, 120, -1, -1, 0, true, "kernel", NetProtocolCapabilities{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}},
+		"UDP-Lite":  {"UDP-Lite", 1024, 0, 57, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, true, false, false, true, true, true, false}},
+		"PING":      {"PING", 904, 0, -1, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, false, true, false, false, true, true, true, true, false, true, true, true, true, true, false}},
+		"RAW":       {"RAW", 912, 0, -1, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, false, true, true, true, true, false, false}},
+		"UDP":       {"UDP", 1024, 73, 57, -1, 0, true, "kernel", NetProtocolCapabilities{true, true, true, false, true, true, true, false, true, true, true, true, true, false, false, true, true, true, false}},
+		"TCP":       {"TCP", 1984, 93064, 1225378, 1, 320, true, "kernel", NetProtocolCapabilities{true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true}},
+		"NETLINK":   {"NETLINK", 1040, 16, -1, -1, 0, false, "kernel", NetProtocolCapabilities{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}},
+	}
+
+	if want, have := len(lines), len(protocolStats); want != have {
+		t.Errorf("want %d parsed net/protocols lines, have %d", want, have)
+	}
+	for _, line := range protocolStats {
+		if want, have := lines[line.Name], line; want != have {
+			t.Errorf("%s: want %v, have %v", line.Name, want, have)
+		}
+	}
+}
diff --git a/net_tcp_test.go b/net_tcp_test.go
new file mode 100644
index 0000000..f00d4b5
--- /dev/null
+++ b/net_tcp_test.go
@@ -0,0 +1,169 @@
+// 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 (
+	"net"
+	"reflect"
+	"testing"
+)
+
+func Test_newNetTCP(t *testing.T) {
+	tests := []struct {
+		name    string
+		file    string
+		want    NetTCP
+		wantErr bool
+	}{
+		{
+			name: "tcp file found, no error should come up",
+			file: "fixtures/proc/net/tcp",
+			want: []*netIPSocketLine{
+				&netIPSocketLine{
+					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,
+				},
+				&netIPSocketLine{
+					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,
+				},
+				&netIPSocketLine{
+					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,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "tcp6 file found, no error should come up",
+			file: "fixtures/proc/net/tcp6",
+			want: []*netIPSocketLine{
+				&netIPSocketLine{
+					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,
+				},
+				&netIPSocketLine{
+					Sl:        6073,
+					LocalAddr: net.IP{254, 128, 0, 0, 0, 0, 0, 0, 86, 225, 173, 255, 254, 124, 102, 9},
+					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,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "error case - file not found",
+			file:    "somewhere over the rainbow",
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse error",
+			file:    "fixtures/proc/net/tcp_broken",
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := newNetTCP(tt.file)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("newNetTCP() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("newNetTCP() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_newNetTCPSummary(t *testing.T) {
+	tests := []struct {
+		name    string
+		file    string
+		want    *NetTCPSummary
+		wantErr bool
+	}{
+		{
+			name:    "tcp file found, no error should come up",
+			file:    "fixtures/proc/net/tcp",
+			want:    &NetTCPSummary{TxQueueLength: 2, RxQueueLength: 2, UsedSockets: 3},
+			wantErr: false,
+		},
+		{
+			name:    "tcp6 file found, no error should come up",
+			file:    "fixtures/proc/net/tcp6",
+			want:    &NetTCPSummary{TxQueueLength: 0, RxQueueLength: 0, UsedSockets: 2},
+			wantErr: false,
+		},
+		{
+			name:    "error case - file not found",
+			file:    "somewhere over the rainbow",
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "error case - parse error",
+			file:    "fixtures/proc/net/tcp_broken",
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := newNetTCPSummary(tt.file)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("newNetTCPSummary() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("newNetTCPSummary() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/net_udp_test.go b/net_udp_test.go
index 363fdb2..f3d56d0 100644
--- a/net_udp_test.go
+++ b/net_udp_test.go
@@ -19,94 +19,6 @@ import (
 	"testing"
 )
 
-func Test_parseNetUDPLine(t *testing.T) {
-	tests := []struct {
-		fields  []string
-		name    string
-		want    *netUDPLine
-		wantErr bool
-	}{
-		{
-			name:   "reading valid lines, no issue should happened",
-			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},
-				LocalPort: 0,
-				RemAddr:   net.IP{0, 0, 0, 0},
-				RemPort:   0,
-				St:        10,
-				TxQueue:   23,
-				RxQueue:   42,
-				UID:       1000,
-			},
-		},
-		{
-			name:    "error case - invalid line - number of fields/columns < 8",
-			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "0:0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse sl - not a valid uint",
-			fields:  []string{"a:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse local_address - not a valid hex",
-			fields:  []string{"1:", "0000000O:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse rem_address - not a valid hex",
-			fields:  []string{"1:", "00000000:0000", "0000000O:0000", "07", "00000000:00000001", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - cannot parse line - missing colon",
-			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse tx_queue - not a valid hex",
-			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "DEADCODE:00000001", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse rx_queue - not a valid hex",
-			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:FEEDCODE", "0:0", "0", "0"},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name:    "error case - parse UID - not a valid uint",
-			fields:  []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "-10"},
-			want:    nil,
-			wantErr: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := parseNetUDPLine(tt.fields)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("parseNetUDPLine() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			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)
-			}
-		})
-	}
-}
-
 func Test_newNetUDP(t *testing.T) {
 	tests := []struct {
 		name    string
@@ -117,8 +29,8 @@ func Test_newNetUDP(t *testing.T) {
 		{
 			name: "udp file found, no error should come up",
 			file: "fixtures/proc/net/udp",
-			want: []*netUDPLine{
-				&netUDPLine{
+			want: []*netIPSocketLine{
+				&netIPSocketLine{
 					Sl:        0,
 					LocalAddr: net.IP{10, 0, 0, 5},
 					LocalPort: 22,
@@ -129,7 +41,7 @@ func Test_newNetUDP(t *testing.T) {
 					RxQueue:   1,
 					UID:       0,
 				},
-				&netUDPLine{
+				&netIPSocketLine{
 					Sl:        1,
 					LocalAddr: net.IP{0, 0, 0, 0},
 					LocalPort: 22,
@@ -140,7 +52,7 @@ func Test_newNetUDP(t *testing.T) {
 					RxQueue:   0,
 					UID:       0,
 				},
-				&netUDPLine{
+				&netIPSocketLine{
 					Sl:        2,
 					LocalAddr: net.IP{0, 0, 0, 0},
 					LocalPort: 22,
@@ -157,8 +69,8 @@ func Test_newNetUDP(t *testing.T) {
 		{
 			name: "udp6 file found, no error should come up",
 			file: "fixtures/proc/net/udp6",
-			want: []*netUDPLine{
-				&netUDPLine{
+			want: []*netIPSocketLine{
+				&netIPSocketLine{
 					Sl:        1315,
 					LocalAddr: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 					LocalPort: 5355,
@@ -169,9 +81,9 @@ func Test_newNetUDP(t *testing.T) {
 					RxQueue:   0,
 					UID:       981,
 				},
-				&netUDPLine{
+				&netIPSocketLine{
 					Sl:        6073,
-					LocalAddr: net.IP{0, 0, 128, 254, 0, 0, 0, 0, 255, 173, 225, 86, 9, 102, 124, 254},
+					LocalAddr: net.IP{254, 128, 0, 0, 0, 0, 0, 0, 86, 225, 173, 255, 254, 124, 102, 9},
 					LocalPort: 51073,
 					RemAddr:   net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 					RemPort:   0,
diff --git a/proc_cgroup_test.go b/proc_cgroup_test.go
index f530edb..00ab2b4 100644
--- a/proc_cgroup_test.go
+++ b/proc_cgroup_test.go
@@ -56,13 +56,13 @@ func TestParseCgroupString(t *testing.T) {
 			},
 		},
 		{
-			name:      "extra fields (such as those added by later kernel versions)",
-			s:         "0::/:foobar",
+			name:      "path containing colons",
+			s:         "0::/some/long/path:foobar",
 			shouldErr: false,
 			cgroup: &Cgroup{
 				HierarchyID: 0,
 				Controllers: nil,
-				Path:        "/",
+				Path:        "/some/long/path:foobar",
 			},
 		},
 		{
diff --git a/proc_limits_test.go b/proc_limits_test.go
index 9148e28..bb8ae61 100644
--- a/proc_limits_test.go
+++ b/proc_limits_test.go
@@ -28,10 +28,10 @@ func TestLimits(t *testing.T) {
 
 	for _, test := range []struct {
 		name string
-		want int64
-		have int64
+		want uint64
+		have uint64
 	}{
-		{name: "cpu time", want: -1, have: l.CPUTime},
+		{name: "cpu time", want: 18446744073709551615, have: l.CPUTime},
 		{name: "open files", want: 2048, have: l.OpenFiles},
 		{name: "msgqueue size", want: 819200, have: l.MsqqueueSize},
 		{name: "nice priority", want: 0, have: l.NicePriority},
diff --git a/slab_test.go b/slab_test.go
new file mode 100644
index 0000000..8ed8e89
--- /dev/null
+++ b/slab_test.go
@@ -0,0 +1,64 @@
+// 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 (
+	"testing"
+)
+
+func TestSlabInfo(t *testing.T) {
+	slabs, err := getProcFixtures(t).SlabInfo()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if numSlabs := len(slabs.Slabs); numSlabs != 300 {
+		t.Errorf("expected 300 slabs, got %v", numSlabs)
+	}
+	if name := slabs.Slabs[0].Name; name != "pid_3" {
+		t.Errorf("Expected slab name to be 'pid_3', got %v", name)
+	}
+	if objActive := slabs.Slabs[0].ObjActive; objActive != 375 {
+		t.Errorf("Expected slab objects active to be 375, got %v", objActive)
+	}
+	if objNum := slabs.Slabs[0].ObjNum; objNum != 532 {
+		t.Errorf("Expected slab objects number to be 532, got %v", objNum)
+	}
+	if objSize := slabs.Slabs[0].ObjSize; objSize != 576 {
+		t.Errorf("Expected slab objects Size to be 576, got %+v", objSize)
+	}
+	if objPerSlab := slabs.Slabs[0].ObjPerSlab; objPerSlab != 28 {
+		t.Errorf("Expected slab objects per slab to be 28, got %v", objPerSlab)
+	}
+	if pagesPerSlab := slabs.Slabs[0].PagesPerSlab; pagesPerSlab != 4 {
+		t.Errorf("Expected pages per slab to be 4, got %v", pagesPerSlab)
+	}
+	if limit := slabs.Slabs[0].Limit; limit != 0 {
+		t.Errorf("Expected limit to be 0, got %v", limit)
+	}
+	if batch := slabs.Slabs[0].Batch; batch != 0 {
+		t.Errorf("Expected batch to be 0, got %v", batch)
+	}
+	if sharedFactor := slabs.Slabs[0].SharedFactor; sharedFactor != 0 {
+		t.Errorf("Expected shared factor to be 0, got %v", sharedFactor)
+	}
+	if slabActive := slabs.Slabs[0].SlabActive; slabActive != 19 {
+		t.Errorf("Expected slab active to be 19, got %v", slabActive)
+	}
+	if slabNum := slabs.Slabs[0].SlabNum; slabNum != 19 {
+		t.Errorf("Expected slab num to be 19, got %v", slabNum)
+	}
+	if sharedAvail := slabs.Slabs[0].SharedAvail; sharedAvail != 0 {
+		t.Errorf("Expected shared available to be 0, got %v", sharedAvail)
+	}
+}
diff --git a/sysfs/vmstat_numa_test.go b/sysfs/vmstat_numa_test.go
new file mode 100644
index 0000000..64977b9
--- /dev/null
+++ b/sysfs/vmstat_numa_test.go
@@ -0,0 +1,44 @@
+// 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 sysfs
+
+import (
+	"testing"
+)
+
+func TestParseVMStatNUMA(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	vmstat, err := fs.VMStatNUMA()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want, got := uint64(1), vmstat[1].NrFreePages; want != got {
+		t.Errorf("want vmstat stat nr_free_pages value %d, got %d", want, got)
+	}
+
+	if want, got := uint64(5), vmstat[1].NrZoneActiveFile; want != got {
+		t.Errorf("want numa stat nr_zone_active_file %d, got %d", want, got)
+	}
+	if want, got := uint64(7), vmstat[2].NrFreePages; want != got {
+		t.Errorf("want vmstat stat nr_free_pages value %d, got %d", want, got)
+	}
+
+	if want, got := uint64(11), vmstat[2].NrZoneActiveFile; want != got {
+		t.Errorf("want numa stat nr_zone_active_file %d, got %d", want, got)
+	}
+}
diff --git a/xfs/parse_test.go b/xfs/parse_test.go
index b799f4b..4989eca 100644
--- a/xfs/parse_test.go
+++ b/xfs/parse_test.go
@@ -199,8 +199,8 @@ func TestParseStats(t *testing.T) {
 			s:    "rw 1 2",
 			stats: &xfs.Stats{
 				ReadWrite: xfs.ReadWriteStats{
-					Read:  1,
-					Write: 2,
+					Write: 1,
+					Read:  2,
 				},
 			},
 		},
@@ -577,8 +577,8 @@ func TestParseStats(t *testing.T) {
 					ForceSleep:        739,
 				},
 				ReadWrite: xfs.ReadWriteStats{
-					Read:  107739,
-					Write: 94045,
+					Write: 107739,
+					Read:  94045,
 				},
 				AttributeOperation: xfs.AttributeOperationStats{
 					Get:    4,