Repository: prometheus/procfs
Pull request #164 (2019-05-23 17:19:05)

Add support for InfiniBand
The Prometheus node exporter collects metrics for the InfiniBand network protocol including the amount of packets sent and received, the number of times the link has been downed and how many times the link has recovered from an error state.

Reading all those information from sysfs is better placed in the procfs library. Also collect the state, physical state, and the rate.

bdrung: Thanks for the review. I will address those points. @mdlayher, do you really prefer to have a switch statement for the 16 different values? This list might grow. If the counter fields are regular values, what should I do if the system does not have this counter?
bdrung: I looked at https://github.com/prometheus/node_exporter/blob/master/collector/infiniband_linux.go and borrowed the workarounds and the legacy counters.
bdrung: I changed the two parse functions to take a string as parameter (instead of a path to the file).
discordianfish: Tend to agree with @mdlayher. What about using a temporary map to iterate over instead of using reflect to iterate over the map?

Patch:
diff --git a/Makefile.common b/Makefile.common
index 6b94f83..d7aea1b 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -248,7 +248,9 @@ proto:
 ifdef GOLANGCI_LINT
 $(GOLANGCI_LINT):
 	mkdir -p $(FIRST_GOPATH)/bin
-	curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
+	curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \
+		| sed -e '/install -d/d' \
+		| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
 endif
 
 ifdef GOVENDOR
diff --git a/fixtures.ttar b/fixtures.ttar
index 951d909..60b334f 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -3,7 +3,7 @@ Directory: fixtures
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc
-Mode: 755
+Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/26231
 Mode: 755
@@ -21,6 +21,11 @@ Mode: 644
 Path: fixtures/proc/26231/cwd
 SymlinkTo: /usr/bin
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/environ
+Lines: 1
+PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNULLBYTEHOSTNAME=cd24e11f73a5NULLBYTETERM=xtermNULLBYTEGOLANG_VERSION=1.12.5NULLBYTEGOPATH=/goNULLBYTEHOME=/rootNULLBYTEEOF
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26231/exe
 SymlinkTo: /usr/bin/vim
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -356,32 +361,62 @@ debug 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/mdstat
-Lines: 26
+Lines: 56
 Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
-md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9]
+
+md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9] sdd1[10](S) sdd2[11](S)
       5853468288 blocks super 1.2 level 6, 64k chunk, algorithm 2 [8/8] [UUUUUUUU]
 
 md127 : active raid1 sdi2[0] sdj2[1]
       312319552 blocks [2/2] [UU]
 
-md0 : active raid1 sdk[2](S) sdi1[0] sdj1[1]
+md0 : active raid1 sdi1[0] sdj1[1]
       248896 blocks [2/2] [UU]
 
-md4 : inactive raid1 sda3[0] sdb3[1]
+md4 : inactive raid1 sda3[0](F) sdb3[1](S)
       4883648 blocks [2/2] [UU]
 
-md6 : active raid1 sdb2[2] sda2[0]
+md6 : active raid1 sdb2[2](F) sdc[1](S) sda2[0]
       195310144 blocks [2/1] [U_]
       [=>...................]  recovery =  8.5% (16775552/195310144) finish=17.0min speed=259783K/sec
 
-md8 : active raid1 sdb1[1] sda1[0]
+md8 : active raid1 sdb1[1] sda1[0] sdc[2](S) sde[3](S)
       195310144 blocks [2/2] [UU]
       [=>...................]  resync =  8.5% (16775552/195310144) finish=17.0min speed=259783K/sec
 
-md7 : active raid6 sdb1[0] sde1[3] sdd1[2] sdc1[1]
+md7 : active raid6 sdb1[0] sde1[3] sdd1[2] sdc1[1](F)
       7813735424 blocks super 1.2 level 6, 512k chunk, algorithm 2 [4/3] [U_UU]
       bitmap: 0/30 pages [0KB], 65536KB chunk
 
+md9 : active raid1 sdc2[2] sdd2[3] sdb2[1] sda2[0] sde[4](F) sdf[5](F) sdg[6](S)
+      523968 blocks super 1.2 [4/4] [UUUU]
+      resync=DELAYED
+
+md10 : active raid0 sda1[0] sdb1[1]
+       314159265 blocks 64k chunks
+
+md11 : active (auto-read-only) raid1 sdb2[0] sdc2[1] sdc3[2](F) hda[4](S) ssdc2[3](S)
+      4190208 blocks super 1.2 [2/2] [UU]
+      resync=PENDING
+
+md12 : active raid0 sdc2[0] sdd2[1]
+      3886394368 blocks super 1.2 512k chunks
+
+md126 : active raid0 sdb[1] sdc[0]
+      1855870976 blocks super external:/md127/0 128k chunks
+
+md219 : inactive sdb[2](S) sdc[1](S) sda[0](S)
+        7932 blocks super external:imsm
+
+md00 : active raid0 xvdb[0]
+      4186624 blocks super 1.2 256k chunks
+
+md120 : active linear sda1[1] sdb1[0]
+        2095104 blocks super 1.2 0k rounding
+
+md101 : active (read-only) raid0 sdb[2] sdd[1] sdc[0]
+      322560 blocks super 1.2 512k chunks
+
 unused devices: <none>
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -609,6 +644,232 @@ Mode: 664
 Directory: fixtures/sys/class
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/board_id
+Lines: 1
+SM_1141000001000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/fw_ver
+Lines: 1
+2.31.5050
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/hca_type
+Lines: 1
+MT4099
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0/ports
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0/ports/1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/excessive_buffer_overrun_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/link_downed
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/link_error_recovery
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/local_link_integrity_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_constraint_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_data
+Lines: 1
+2221223609
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_packets
+Lines: 1
+87169372
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_remote_physical_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_rcv_switch_relay_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_xmit_constraint_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_xmit_data
+Lines: 1
+26509113295
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_xmit_discards
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_xmit_packets
+Lines: 1
+85734114
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/port_xmit_wait
+Lines: 1
+3599
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/counters/symbol_error
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/phys_state
+Lines: 1
+5: LinkUp
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/rate
+Lines: 1
+40 Gb/sec (4X QDR)
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/1/state
+Lines: 1
+4: ACTIVE
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0/ports/2
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/excessive_buffer_overrun_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/link_downed
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/link_error_recovery
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/local_link_integrity_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_constraint_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_data
+Lines: 1
+2460436784
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_packets
+Lines: 1
+89332064
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_remote_physical_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_rcv_switch_relay_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_xmit_constraint_errors
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_xmit_data
+Lines: 1
+26540356890
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_xmit_discards
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_xmit_packets
+Lines: 1
+88622850
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/port_xmit_wait
+Lines: 1
+3846
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/counters/symbol_error
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/phys_state
+Lines: 1
+5: LinkUp
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/rate
+Lines: 1
+40 Gb/sec (4X QDR)
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/infiniband/mlx4_0/ports/2/state
+Lines: 1
+4: ACTIVE
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/class/net
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1806,3 +2067,287 @@ Lines: 1
 extent_alloc 2 0 0 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/statistics
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/statistics/scsi_tgt_port
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/in_cmds
+Lines: 1
+104950
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/read_mbytes
+Lines: 1
+20095
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/write_mbytes
+Lines: 1
+71235
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/iblock_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/iblock_0/block_lio_rbd1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0/795b7c7026
+SymlinkTo: ../../../../../../target/core/iblock_0/block_lio_rbd1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/iblock_0/block_lio_rbd1/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/iblock_0/block_lio_rbd1/udev_path
+Lines: 1
+/dev/rbd1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/statistics
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/in_cmds
+Lines: 1
+204950
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/read_mbytes
+Lines: 1
+10325
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/write_mbytes
+Lines: 1
+40325
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/rd_mcp_119
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/rd_mcp_119/ramdisk_lio_1G
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0/7f4a4eb56d
+SymlinkTo: ../../../../../../target/core/rd_mcp_119/ramdisk_lio_1G
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/rd_mcp_119/ramdisk_lio_1G/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/rd_mcp_119/ramdisk_lio_1G/udev_path
+Lines: 0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/statistics
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/in_cmds
+Lines: 1
+301950
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/read_mbytes
+Lines: 1
+10195
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/write_mbytes
+Lines: 1
+30195
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/fileio_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/fileio_1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/fileio_1/file_lio_1G
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0/fff5e16686
+SymlinkTo: ../../../../../../target/core/fileio_1/file_lio_1G
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/fileio_1/file_lio_1G/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/fileio_1/file_lio_1G/udev_path
+Lines: 1
+/home/iscsi/file_back_1G
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/statistics
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/statistics/scsi_tgt_port
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/in_cmds
+Lines: 1
+1234
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/read_mbytes
+Lines: 1
+1504
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/statistics/scsi_tgt_port/write_mbytes
+Lines: 1
+4733
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/rbd_0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/kernel/config/target/core/rbd_0/iscsi-images-demo
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0/eba1edf893
+SymlinkTo: ../../../../../../target/core/rbd_0/iscsi-images-demo
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/rbd_0/iscsi-images-demo/enable
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/kernel/config/target/core/rbd_0/iscsi-images-demo/udev_path
+Lines: 1
+/dev/rbd/iscsi-images/demo
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/rbd
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/rbd/0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/rbd/0/pool
+Lines: 1
+iscsi-images
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/rbd/0/name
+Lines: 1
+demo
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/rbd/1
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/rbd/1/pool
+Lines: 1
+wrong-images
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/rbd/1/name
+Lines: 1
+wrong
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index c66a1cf..7ddfd6b 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -25,6 +25,9 @@ const (
 
 	// DefaultSysMountPoint is the common mount point of the sys filesystem.
 	DefaultSysMountPoint = "/sys"
+
+	// DefaultConfigfsMountPoint is the commont mount point of the configfs
+	DefaultConfigfsMountPoint = "/sys/kernel/config"
 )
 
 // FS represents a pseudo-filesystem, normally /proc or /sys, which provides an
diff --git a/internal/util/valueparser.go b/internal/util/valueparser.go
index 4ecf5a7..ac93cb4 100644
--- a/internal/util/valueparser.go
+++ b/internal/util/valueparser.go
@@ -52,6 +52,25 @@ func (vp *ValueParser) PInt64() *int64 {
 	return &v
 }
 
+// PUInt64 interprets the underlying value as an uint64 and returns a pointer to
+// that value.
+func (vp *ValueParser) PUInt64() *uint64 {
+	if vp.err != nil {
+		return nil
+	}
+
+	// A base value of zero makes ParseInt infer the correct base using the
+	// string's prefix, if any.
+	const base = 0
+	v, err := strconv.ParseUint(vp.v, base, 64)
+	if err != nil {
+		vp.err = err
+		return nil
+	}
+
+	return &v
+}
+
 // Err returns the last error, if any, encountered by the ValueParser.
 func (vp *ValueParser) Err() error {
 	return vp.err
diff --git a/iscsi/get.go b/iscsi/get.go
new file mode 100644
index 0000000..489a655
--- /dev/null
+++ b/iscsi/get.go
@@ -0,0 +1,253 @@
+// Copyright 2019 The Prometheus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iscsi
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// GetStats is the main iscsi status information func
+// building the path and prepare info for enable iscsi
+func GetStats(iqnPath string) (*Stats, error) {
+	var istats Stats
+
+	istats.Name = filepath.Base(iqnPath)
+	istats.RootPath = filepath.Dir(iqnPath)
+
+	matches, err := filepath.Glob(filepath.Join(iqnPath, "tpgt*"))
+	if err != nil {
+		return nil, fmt.Errorf("iscsi: GetStats: get TPGT path error %v", err)
+	}
+	istats.Tpgt = make([]TPGT, len(matches))
+
+	for i, tpgtPath := range matches {
+		istats.Tpgt[i].Name = filepath.Base(tpgtPath)
+		istats.Tpgt[i].TpgtPath = tpgtPath
+		istats.Tpgt[i].IsEnable, _ = isPathEnable(tpgtPath)
+		if istats.Tpgt[i].IsEnable {
+			matchesLunsPath, _ := filepath.Glob(filepath.Join(tpgtPath, "lun/lun*"))
+
+			for _, lunPath := range matchesLunsPath {
+				lun, err := getLunLinkTarget(lunPath)
+				if err == nil {
+					istats.Tpgt[i].Luns = append(istats.Tpgt[i].Luns, lun)
+				}
+			}
+		}
+	}
+	return &istats, nil
+}
+
+// isPathEnable is a utility function
+// check if the file "enable" contain enable message
+func isPathEnable(path string) (bool, error) {
+	enableReadout, err := ioutil.ReadFile(filepath.Join(path, "enable"))
+	if err != nil {
+		return false, fmt.Errorf("iscsi: isPathEnable ReadFile error %v", err)
+	}
+	isEnable, err := strconv.ParseBool(strings.TrimSpace(string(enableReadout)))
+	if err != nil {
+		return false, fmt.Errorf("iscsi: isPathEnable ParseBool error %v", err)
+	}
+	return isEnable, nil
+}
+
+func getLunLinkTarget(lunPath string) (lunObject LUN, err error) {
+	lunObject.Name = filepath.Base(lunPath)
+	lunObject.LunPath = lunPath
+	files, err := ioutil.ReadDir(lunPath)
+	if err != nil {
+		return lunObject, fmt.Errorf("getLunLinkTarget: ReadDir path %s error %v", lunPath, err)
+	}
+	for _, file := range files {
+		fileInfo, _ := os.Lstat(filepath.Join(lunPath, file.Name()))
+		if fileInfo.Mode()&os.ModeSymlink != 0 {
+			target, err := os.Readlink(filepath.Join(lunPath, fileInfo.Name()))
+			if err != nil {
+				return lunObject, fmt.Errorf("getLunLinkTarget: Readlink err %v", err)
+			}
+			targetPath, objectName := filepath.Split(target)
+			_, typeWithNumber := filepath.Split(filepath.Clean(targetPath))
+
+			underscore := strings.LastIndex(typeWithNumber, "_")
+
+			if underscore != -1 {
+				lunObject.Backstore = typeWithNumber[:underscore]
+				lunObject.TypeNumber = typeWithNumber[underscore+1:]
+			}
+
+			lunObject.ObjectName = objectName
+			return lunObject, nil
+		}
+	}
+	return lunObject, errors.New("iscsi: getLunLinkTarget: Lun Link does not exist")
+}
+
+// ReadWriteOPS read and return the stat of read and write in megabytes,
+// and total commands that send to the target
+func ReadWriteOPS(iqnPath string, tpgt string, lun string) (readmb uint64,
+	writemb uint64, iops uint64, err error) {
+
+	readmbPath := filepath.Join(iqnPath, tpgt, "lun", lun,
+		"statistics/scsi_tgt_port/read_mbytes")
+	readmb, err = util.ReadUintFromFile(readmbPath)
+	if err != nil {
+		return 0, 0, 0, fmt.Errorf("iscsi: ReadWriteOPS: read_mbytes error file %s and %v",
+			readmbPath, err)
+	}
+
+	writembPath := filepath.Join(iqnPath, tpgt, "lun", lun,
+		"statistics/scsi_tgt_port/write_mbytes")
+	writemb, err = util.ReadUintFromFile(writembPath)
+	if err != nil {
+		return 0, 0, 0, fmt.Errorf("iscsi: ReadWriteOPS: write_mbytes error file %s and %v",
+			writembPath, err)
+	}
+
+	iopsPath := filepath.Join(iqnPath, tpgt, "lun", lun,
+		"statistics/scsi_tgt_port/in_cmds")
+	iops, err = util.ReadUintFromFile(iopsPath)
+	if err != nil {
+		return 0, 0, 0, fmt.Errorf("iscsi: ReadWriteOPS: in_cmds error file %s and %v",
+			iopsPath, err)
+	}
+
+	return readmb, writemb, iops, nil
+}
+
+// GetFileioUdev is getting the actual info to build up
+// the FILEIO data and match with the enable target
+func (fileio FILEIO) GetFileioUdev(targetCorePath string, fileioNumber string,
+	objectName string) (fio *FILEIO, err error) {
+
+	fileio.Name = "fileio_" + fileioNumber
+	fileio.Fnumber = fileioNumber
+	fileio.ObjectName = objectName
+
+	udevPath := filepath.Join(targetCorePath, fileio.Name, fileio.ObjectName, "udev_path")
+
+	if _, err := os.Stat(udevPath); os.IsNotExist(err) {
+		return nil, fmt.Errorf("iscsi: GetFileioUdev: fileio_%s is missing file name", fileio.Fnumber)
+	}
+	filename, err := ioutil.ReadFile(udevPath)
+	if err != nil {
+		return nil, fmt.Errorf("iscsi: GetFileioUdev: Cannot read filename from udev link :%s", udevPath)
+	}
+	fileio.Filename = strings.TrimSpace(string(filename))
+
+	return &fileio, nil
+}
+
+// GetIblockUdev is getting the actual info to build up
+// the IBLOCK data and match with the enable target
+func (iblock IBLOCK) GetIblockUdev(targetCorePath string, iblockNumber string,
+	objectName string) (ib *IBLOCK, err error) {
+
+	iblock.Name = "iblock_" + iblockNumber
+	iblock.Bnumber = iblockNumber
+	iblock.ObjectName = objectName
+
+	udevPath := filepath.Join(targetCorePath, iblock.Name, iblock.ObjectName, "udev_path")
+
+	if _, err := os.Stat(udevPath); os.IsNotExist(err) {
+		return nil, fmt.Errorf("iscsi: GetIBlockUdev: iblock_%s is missing file name", iblock.Bnumber)
+	}
+	filename, err := ioutil.ReadFile(udevPath)
+	if err != nil {
+		return nil, fmt.Errorf("iscsi: GetIBlockUdev: Cannot read iblock from udev link :%s", udevPath)
+	}
+	iblock.Iblock = strings.TrimSpace(string(filename))
+
+	return &iblock, nil
+}
+
+// GetRBDMatch is getting the actual info to build up
+// the RBD data and match with the enable target
+func (rbd RBD) GetRBDMatch(sysDevicePath string, rbdNumber string, poolImage string) (r *RBD, err error) {
+
+	rbd.Name = "rbd_" + rbdNumber
+	rbd.Rnumber = rbdNumber
+
+	systemRbds, err := filepath.Glob(filepath.Join(sysDevicePath, "/devices/rbd/[0-9]*"))
+	if err != nil {
+		return nil, fmt.Errorf("iscsi: GetRBDMatch: Cannot find any rbd block")
+	}
+
+	for systemRbdNumber, systemRbdPath := range systemRbds {
+		var systemPool, systemImage string = "", ""
+		systemPoolPath := filepath.Join(systemRbdPath, "pool")
+		if _, err := os.Stat(systemPoolPath); os.IsNotExist(err) {
+			continue
+		}
+		bSystemPool, err := ioutil.ReadFile(systemPoolPath)
+		if err != nil {
+			continue
+		} else {
+			systemPool = strings.TrimSpace(string(bSystemPool))
+		}
+
+		systemImagePath := filepath.Join(systemRbdPath, "name")
+		if _, err := os.Stat(systemImagePath); os.IsNotExist(err) {
+			continue
+		}
+		bSystemImage, err := ioutil.ReadFile(systemImagePath)
+		if err != nil {
+			continue
+		} else {
+			systemImage = strings.TrimSpace(string(bSystemImage))
+		}
+
+		if strings.Compare(strconv.FormatInt(int64(systemRbdNumber), 10), rbdNumber) == 0 &&
+			matchPoolImage(systemPool, systemImage, poolImage) {
+			rbd.Pool = systemPool
+			rbd.Image = systemImage
+			return &rbd, nil
+		}
+	}
+	return nil, nil
+}
+
+// GetRDMCPPath is getting the actual info to build up RDMCP data
+func (rdmcp RDMCP) GetRDMCPPath(targetCorePath string, rdmcpNumber string, objectName string) (r *RDMCP, err error) {
+	rdmcp.Name = "rd_mcp_" + rdmcpNumber
+	rdmcp.ObjectName = objectName
+
+	rdmcpPath := filepath.Join(targetCorePath, rdmcp.Name, rdmcp.ObjectName)
+
+	if _, err := os.Stat(rdmcpPath); os.IsNotExist(err) {
+		return nil, fmt.Errorf("iscsi: GetRDMCPPath: %s does not exist", rdmcpPath)
+	}
+	isEnable, err := isPathEnable(rdmcpPath)
+	if err != nil {
+		return nil, fmt.Errorf("iscsi: GetRDMCPPath: error %v", err)
+	}
+	if isEnable {
+		return &rdmcp, nil
+	}
+	return nil, nil
+}
+
+func matchPoolImage(pool string, image string, matchPoolImage string) (isEqual bool) {
+	var poolImage = fmt.Sprintf("%s-%s", pool, image)
+	return strings.Compare(poolImage, matchPoolImage) == 0
+}
diff --git a/iscsi/iscsi.go b/iscsi/iscsi.go
new file mode 100644
index 0000000..a155049
--- /dev/null
+++ b/iscsi/iscsi.go
@@ -0,0 +1,126 @@
+// Copyright 2019 The Prometheus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iscsi
+
+import (
+	"path/filepath"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/fs"
+)
+
+// iscsi target started with /sys/kernel/config/target/iscsi/iqn*
+// configfs + target/iscsi/iqn*
+// IqnGlob is representing all the possible IQN
+const IqnGlob = "target/iscsi/iqn*"
+
+// TargetCore static path /sys/kernel/config/target/core for node_exporter
+// reading runtime status
+const TargetCore = "target/core"
+
+// FS represents the pseudo-filesystem configfs, which provides an interface to
+// iscsi kernel data structures in /sys/kernel/config.
+type FS struct {
+	configfs *fs.FS
+}
+
+// NewFS returns a new configfs mounted under the given mount point. It will
+// error and return empty FS if the mount point can't be read. For the ease of
+// use, an empty string parameter configfsMountPoint will call internal fs for
+// the default sys path as /sys/kernel/config
+func NewFS(configfsMountPoint string) (FS, error) {
+	if strings.TrimSpace(configfsMountPoint) == "" {
+		configfsMountPoint = fs.DefaultConfigfsMountPoint
+	}
+	configfs, err := fs.NewFS(configfsMountPoint)
+	if err != nil {
+		return FS{}, err
+	}
+	return FS{&configfs}, nil
+}
+
+// ISCSIStats getting iscsi runtime information
+func (fs FS) ISCSIStats() ([]*Stats, error) {
+	matches, err := filepath.Glob(fs.configfs.Path(IqnGlob))
+	if err != nil {
+		return nil, err
+	}
+
+	stats := make([]*Stats, 0, len(matches))
+	for _, iqnPath := range matches {
+		// stats
+		s, err := GetStats(iqnPath)
+		if err != nil {
+			return nil, err
+		}
+		stats = append(stats, s)
+	}
+
+	return stats, nil
+}
+
+// TPGT struct for sys target portal group tag info
+type TPGT struct {
+	Name     string // name of the tpgt group
+	TpgtPath string // file path of tpgt
+	IsEnable bool   // is the tpgt enable
+	Luns     []LUN  // the Luns that tpgt has
+}
+
+// LUN struct for sys logical unit number info
+type LUN struct {
+	Name       string // name of the lun
+	LunPath    string // file path of the lun
+	Backstore  string // backstore of the lun
+	ObjectName string // place holder for object
+	TypeNumber string // place holder for number of the device
+}
+
+// FILEIO struct for backstore info
+type FILEIO struct {
+	Name       string // name of the fileio
+	Fnumber    string // number related to the backstore
+	ObjectName string // place holder for object in iscsi object
+	Filename   string // link to the actual file being export
+}
+
+// IBLOCK struct for backstore info
+type IBLOCK struct {
+	Name       string // name of the iblock
+	Bnumber    string // number related to the backstore
+	ObjectName string // place holder for object in iscsi object
+	Iblock     string // link to the actual block being export
+}
+
+// RBD struct for backstore info
+type RBD struct {
+	Name    string // name of the rbd
+	Rnumber string // number related to the backstore
+	Pool    string // place holder for the rbd pool
+	Image   string // place holder for the rbd image
+}
+
+// RDMCP struct for backstore info
+type RDMCP struct {
+	Name       string // name of the rdm_cp
+	ObjectName string // place holder for object name
+}
+
+// Stats struct for all targets info
+type Stats struct {
+	Name     string
+	Tpgt     []TPGT
+	RootPath string
+}
diff --git a/mdstat.go b/mdstat.go
index 71c1067..2af3ada 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -22,8 +22,8 @@ import (
 )
 
 var (
-	statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
-	buildlineRE  = regexp.MustCompile(`\((\d+)/\d+\)`)
+	statusLineRE   = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
+	recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
 )
 
 // MDStat holds info parsed from /proc/mdstat.
@@ -34,8 +34,12 @@ type MDStat struct {
 	ActivityState string
 	// Number of active disks.
 	DisksActive int64
-	// Total number of disks the device consists of.
+	// Total number of disks the device requires.
 	DisksTotal int64
+	// Number of failed disks.
+	DisksFailed int64
+	// Spare disks in the device.
+	DisksSpare int64
 	// Number of blocks the device holds.
 	BlocksTotal int64
 	// Number of blocks on the device that are in sync.
@@ -59,29 +63,38 @@ func (fs FS) MDStat() ([]MDStat, error) {
 
 // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
 // structs containing the relevant info.
-func parseMDStat(mdstatData []byte) ([]MDStat, error) {
+func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 	mdStats := []MDStat{}
-	lines := strings.Split(string(mdstatData), "\n")
-	for i, l := range lines {
-		if strings.TrimSpace(l) == "" || l[0] == ' ' ||
-			strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
+	lines := strings.Split(string(mdStatData), "\n")
+
+	for i, line := range lines {
+		if strings.TrimSpace(line) == "" || line[0] == ' ' ||
+			strings.HasPrefix(line, "Personalities") ||
+			strings.HasPrefix(line, "unused") {
 			continue
 		}
 
-		deviceFields := strings.Fields(l)
+		deviceFields := strings.Fields(line)
 		if len(deviceFields) < 3 {
-			return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l)
+			return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
 		}
-		mdName := deviceFields[0]
-		activityState := deviceFields[2]
+		mdName := deviceFields[0] // mdx
+		state := deviceFields[2]  // active or inactive
 
 		if len(lines) <= i+3 {
-			return mdStats, fmt.Errorf("missing lines for md device %s", mdName)
+			return nil, fmt.Errorf(
+				"error parsing %s: too few lines for md device",
+				mdName,
+			)
 		}
 
-		active, total, size, err := evalStatusLine(lines[i+1])
+		// Failed disks have the suffix (F) & Spare disks have the suffix (S).
+		fail := int64(strings.Count(line, "(F)"))
+		spare := int64(strings.Count(line, "(S)"))
+		active, total, size, err := evalStatusLine(lines[i], lines[i+1])
+
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("error parsing md device lines: %s", err)
 		}
 
 		syncLineIdx := i + 2
@@ -89,20 +102,38 @@ func parseMDStat(mdstatData []byte) ([]MDStat, error) {
 			syncLineIdx++
 		}
 
-		// If device is recovering/syncing at the moment, get the number of currently
+		// If device is syncing at the moment, get the number of currently
 		// synced bytes, otherwise that number equals the size of the device.
 		syncedBlocks := size
-		if strings.Contains(lines[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") {
-			syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
-			if err != nil {
-				return nil, err
+		recovering := strings.Contains(lines[syncLineIdx], "recovery")
+		resyncing := strings.Contains(lines[syncLineIdx], "resync")
+
+		// Append recovery and resyncing state info.
+		if recovering || resyncing {
+			if recovering {
+				state = "recovering"
+			} else {
+				state = "resyncing"
+			}
+
+			// Handle case when resync=PENDING or resync=DELAYED.
+			if strings.Contains(lines[syncLineIdx], "PENDING") ||
+				strings.Contains(lines[syncLineIdx], "DELAYED") {
+				syncedBlocks = 0
+			} else {
+				syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
+				if err != nil {
+					return nil, fmt.Errorf("error parsing sync line in md device %s: %s", mdName, err)
+				}
 			}
 		}
 
 		mdStats = append(mdStats, MDStat{
 			Name:          mdName,
-			ActivityState: activityState,
+			ActivityState: state,
 			DisksActive:   active,
+			DisksFailed:   fail,
+			DisksSpare:    spare,
 			DisksTotal:    total,
 			BlocksTotal:   size,
 			BlocksSynced:  syncedBlocks,
@@ -112,39 +143,51 @@ func parseMDStat(mdstatData []byte) ([]MDStat, error) {
 	return mdStats, nil
 }
 
-func evalStatusLine(statusline string) (active, total, size int64, err error) {
-	matches := statuslineRE.FindStringSubmatch(statusline)
-	if len(matches) != 4 {
-		return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
-	}
+func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, err error) {
 
-	size, err = strconv.ParseInt(matches[1], 10, 64)
+	sizeStr := strings.Fields(statusLine)[0]
+	size, err = strconv.ParseInt(sizeStr, 10, 64)
 	if err != nil {
-		return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
+		return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
+	}
+
+	if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
+		// In the device deviceLine, only disks have a number associated with them in [].
+		total = int64(strings.Count(deviceLine, "["))
+		return total, total, size, nil
+	}
+
+	if strings.Contains(deviceLine, "inactive") {
+		return 0, 0, size, nil
+	}
+
+	matches := statusLineRE.FindStringSubmatch(statusLine)
+	if len(matches) != 4 {
+		return 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
 	}
 
 	total, err = strconv.ParseInt(matches[2], 10, 64)
 	if err != nil {
-		return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
+		return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
 	}
 
 	active, err = strconv.ParseInt(matches[3], 10, 64)
 	if err != nil {
-		return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
+		return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
 	}
 
 	return active, total, size, nil
 }
 
-func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) {
-	matches := buildlineRE.FindStringSubmatch(buildline)
+func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) {
+	matches := recoveryLineRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return 0, fmt.Errorf("unexpected buildline: %s", buildline)
+		return 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
 	}
 
 	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
 	if err != nil {
-		return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
+		return 0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine)
 	}
 
 	return syncedBlocks, nil
diff --git a/mountinfo.go b/mountinfo.go
new file mode 100644
index 0000000..61fa618
--- /dev/null
+++ b/mountinfo.go
@@ -0,0 +1,178 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+)
+
+var validOptionalFields = map[string]bool{
+	"shared":         true,
+	"master":         true,
+	"propagate_from": true,
+	"unbindable":     true,
+}
+
+// A MountInfo is a type that describes the details, options
+// for each mount, parsed from /proc/self/mountinfo.
+// The fields described in each entry of /proc/self/mountinfo
+// is described in the following man page.
+// http://man7.org/linux/man-pages/man5/proc.5.html
+type MountInfo struct {
+	// Unique Id for the mount
+	MountId int
+	// The Id of the parent mount
+	ParentId int
+	// The value of `st_dev` for the files on this FS
+	MajorMinorVer string
+	// The pathname of the directory in the FS that forms
+	// the root for this mount
+	Root string
+	// The pathname of the mount point relative to the root
+	MountPoint string
+	// Mount options
+	Options map[string]string
+	// Zero or more optional fields
+	OptionalFields map[string]string
+	// The Filesystem type
+	FSType string
+	// FS specific information or "none"
+	Source string
+	// Superblock options
+	SuperOptions map[string]string
+}
+
+// Returns part of the mountinfo line, if it exists, else an empty string.
+func getStringSliceElement(parts []string, idx int, defaultValue string) string {
+	if idx >= len(parts) {
+		return defaultValue
+	}
+	return parts[idx]
+}
+
+// Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs.
+func parseMountInfo(r io.Reader) ([]*MountInfo, error) {
+	mounts := []*MountInfo{}
+	scanner := bufio.NewScanner(r)
+	for scanner.Scan() {
+		mountString := scanner.Text()
+		parsedMounts, err := parseMountInfoString(mountString)
+		if err != nil {
+			return nil, err
+		}
+		mounts = append(mounts, parsedMounts)
+	}
+
+	err := scanner.Err()
+	return mounts, err
+}
+
+// Parses a mountinfo file line, and converts it to a MountInfo struct.
+// An important check here is to see if the hyphen separator, as if it does not exist,
+// it means that the line is malformed.
+func parseMountInfoString(mountString string) (*MountInfo, error) {
+	var err error
+
+	// OptionalFields can be zero, hence these checks to ensure we do not populate the wrong values in the wrong spots
+	separatorIndex := strings.Index(mountString, "-")
+	if separatorIndex == -1 {
+		return nil, fmt.Errorf("no separator found in mountinfo string: %s", mountString)
+	}
+	beforeFields := strings.Fields(mountString[:separatorIndex])
+	afterFields := strings.Fields(mountString[separatorIndex+1:])
+	if (len(beforeFields) + len(afterFields)) < 7 {
+		return nil, fmt.Errorf("too few fields")
+	}
+
+	mount := &MountInfo{
+		MajorMinorVer:  getStringSliceElement(beforeFields, 2, ""),
+		Root:           getStringSliceElement(beforeFields, 3, ""),
+		MountPoint:     getStringSliceElement(beforeFields, 4, ""),
+		Options:        mountOptionsParser(getStringSliceElement(beforeFields, 5, "")),
+		OptionalFields: nil,
+		FSType:         getStringSliceElement(afterFields, 0, ""),
+		Source:         getStringSliceElement(afterFields, 1, ""),
+		SuperOptions:   mountOptionsParser(getStringSliceElement(afterFields, 2, "")),
+	}
+
+	mount.MountId, err = strconv.Atoi(getStringSliceElement(beforeFields, 0, ""))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse mount ID")
+	}
+	mount.ParentId, err = strconv.Atoi(getStringSliceElement(beforeFields, 1, ""))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse parent ID")
+	}
+	// Has optional fields, which is a space separated list of values.
+	// Example: shared:2 master:7
+	if len(beforeFields) > 6 {
+		mount.OptionalFields = make(map[string]string)
+		optionalFields := beforeFields[6:]
+		for _, field := range optionalFields {
+			optionSplit := strings.Split(field, ":")
+			target, value := optionSplit[0], ""
+			if len(optionSplit) == 2 {
+				value = optionSplit[1]
+			}
+			// Checks if the 'keys' in the optional fields in the mountinfo line are acceptable.
+			// Allowed 'keys' are shared, master, propagate_from, unbindable.
+			if _, ok := validOptionalFields[target]; ok {
+				mount.OptionalFields[target] = value
+			}
+		}
+	}
+	return mount, nil
+}
+
+// Parses the mount options, superblock options.
+func mountOptionsParser(mountOptions string) map[string]string {
+	opts := make(map[string]string)
+	options := strings.Split(mountOptions, ",")
+	for _, opt := range options {
+		splitOption := strings.Split(opt, "=")
+		if len(splitOption) < 2 {
+			key := splitOption[0]
+			opts[key] = ""
+		} else {
+			key, value := splitOption[0], splitOption[1]
+			opts[key] = value
+		}
+	}
+	return opts
+}
+
+// Retrieves mountinfo information from `/proc/self/mountinfo`.
+func GetMounts() ([]*MountInfo, error) {
+	f, err := os.Open("/proc/self/mountinfo")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return parseMountInfo(f)
+}
+
+// Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`.
+func GetProcMounts(pid int) ([]*MountInfo, error) {
+	f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return parseMountInfo(f)
+}
diff --git a/proc.go b/proc.go
index 8a84301..41c148d 100644
--- a/proc.go
+++ b/proc.go
@@ -247,6 +247,20 @@ func (p Proc) MountStats() ([]*Mount, error) {
 	return parseMountStats(f)
 }
 
+// MountInfo retrieves mount information for mount points in a
+// process's namespace.
+// It supplies information missing in `/proc/self/mounts` and
+// fixes various other problems with that file too.
+func (p Proc) MountInfo() ([]*MountInfo, error) {
+	f, err := os.Open(p.path("mountinfo"))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	return parseMountInfo(f)
+}
+
 func (p Proc) fileDescriptors() ([]string, error) {
 	d, err := os.Open(p.path("fd"))
 	if err != nil {
diff --git a/proc_environ.go b/proc_environ.go
new file mode 100644
index 0000000..7172bb5
--- /dev/null
+++ b/proc_environ.go
@@ -0,0 +1,43 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import (
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+// Environ reads process environments from /proc/<pid>/environ
+func (p Proc) Environ() ([]string, error) {
+	environments := make([]string, 0)
+
+	f, err := os.Open(p.path("environ"))
+	if err != nil {
+		return environments, err
+	}
+	defer f.Close()
+
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return environments, err
+	}
+
+	environments = strings.Split(string(data), "\000")
+	if len(environments) > 0 {
+		environments = environments[:len(environments)-1]
+	}
+
+	return environments, nil
+}
diff --git a/sysfs/class_infiniband.go b/sysfs/class_infiniband.go
new file mode 100644
index 0000000..2600083
--- /dev/null
+++ b/sysfs/class_infiniband.go
@@ -0,0 +1,370 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+const infinibandClassPath = "class/infiniband"
+
+// InfiniBandCounters contains counter values from files in
+// /sys/class/infiniband/<Name>/ports/<Port>/counters or
+// /sys/class/infiniband/<Name>/ports/<Port>/counters_ext
+// for a single port of one InfiniBand device.
+type InfiniBandCounters struct {
+	LegacyPortMulticastRcvPackets  *uint64 // counters_ext/port_multicast_rcv_packets
+	LegacyPortMulticastXmitPackets *uint64 // counters_ext/port_multicast_xmit_packets
+	LegacyPortRcvData64            *uint64 // counters_ext/port_rcv_data_64
+	LegacyPortRcvPackets64         *uint64 // counters_ext/port_rcv_packets_64
+	LegacyPortUnicastRcvPackets    *uint64 // counters_ext/port_unicast_rcv_packets
+	LegacyPortUnicastXmitPackets   *uint64 // counters_ext/port_unicast_xmit_packets
+	LegacyPortXmitData64           *uint64 // counters_ext/port_xmit_data_64
+	LegacyPortXmitPackets64        *uint64 // counters_ext/port_xmit_packets_64
+
+	LinkDowned               *uint64 // counters/link_downed
+	LinkErrorRecovery        *uint64 // counters/link_error_recovery
+	MulticastRcvPackets      *uint64 // counters/multicast_rcv_packets
+	MulticastXmitPackets     *uint64 // counters/multicast_xmit_packets
+	PortRcvConstraintErrors  *uint64 // counters/port_rcv_constraint_errors
+	PortRcvData              *uint64 // counters/port_rcv_data
+	PortRcvDiscards          *uint64 // counters/port_rcv_discards
+	PortRcvErrors            *uint64 // counters/port_rcv_errors
+	PortRcvPackets           *uint64 // counters/port_rcv_packets
+	PortXmitConstraintErrors *uint64 // counters/port_xmit_constraint_errors
+	PortXmitData             *uint64 // counters/port_xmit_data
+	PortXmitDiscards         *uint64 // counters/port_xmit_discards
+	PortXmitPackets          *uint64 // counters/port_xmit_packets
+	PortXmitWait             *uint64 // counters/port_xmit_wait
+	UnicastRcvPackets        *uint64 // counters/unicast_rcv_packets
+	UnicastXmitPackets       *uint64 // counters/unicast_xmit_packets
+}
+
+// InfiniBandPort contains info from files in
+// /sys/class/infiniband/<Name>/ports/<Port>
+// for a single port of one InfiniBand device.
+type InfiniBandPort struct {
+	Name        string
+	Port        uint
+	State       string // String representation from /sys/class/infiniband/<Name>/ports/<Port>/state
+	StateID     uint   // ID from /sys/class/infiniband/<Name>/ports/<Port>/state
+	PhysState   string // String representation from /sys/class/infiniband/<Name>/ports/<Port>/phys_state
+	PhysStateID uint   // String representation from /sys/class/infiniband/<Name>/ports/<Port>/phys_state
+	Rate        uint64 // in bytes/second from /sys/class/infiniband/<Name>/ports/<Port>/rate
+	Counters    InfiniBandCounters
+}
+
+// InfiniBandDevice contains info from files in /sys/class/infiniband for a
+// single InfiniBand device.
+type InfiniBandDevice struct {
+	Name            string
+	BoardID         string // /sys/class/infiniband/<Name>/board_id
+	FirmwareVersion string // /sys/class/infiniband/<Name>/fw_ver
+	HCAType         string // /sys/class/infiniband/<Name>/hca_type
+	Ports           map[uint]InfiniBandPort
+}
+
+// InfiniBandClass is a collection of every InfiniBand device in
+// /sys/class/infiniband.
+//
+// The map keys are the names of the InfiniBand devices.
+type InfiniBandClass map[string]InfiniBandDevice
+
+// InfiniBandClass returns info for all InfiniBand devices read from
+// /sys/class/infiniband.
+func (fs FS) InfiniBandClass() (InfiniBandClass, error) {
+	path := fs.sys.Path(infinibandClassPath)
+
+	dirs, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, fmt.Errorf("failed to list InfiniBand devices at %q: %v", path, err)
+	}
+
+	ibc := make(InfiniBandClass, len(dirs))
+	for _, d := range dirs {
+		device, err := fs.parseInfiniBandDevice(d.Name())
+		if err != nil {
+			return nil, err
+		}
+
+		ibc[device.Name] = *device
+	}
+
+	return ibc, nil
+}
+
+// Parse one InfiniBand device.
+func (fs FS) parseInfiniBandDevice(name string) (*InfiniBandDevice, error) {
+	path := fs.sys.Path(infinibandClassPath, name)
+	device := InfiniBandDevice{Name: name}
+
+	for _, f := range [...]string{"board_id", "fw_ver", "hca_type"} {
+		name := filepath.Join(path, f)
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
+		}
+
+		switch f {
+		case "board_id":
+			device.BoardID = value
+		case "fw_ver":
+			device.FirmwareVersion = value
+		case "hca_type":
+			device.HCAType = value
+		}
+	}
+
+	portsPath := filepath.Join(path, "ports")
+	ports, err := ioutil.ReadDir(portsPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to list InfiniBand ports at %q: %v", portsPath, err)
+	}
+
+	device.Ports = make(map[uint]InfiniBandPort, len(ports))
+	for _, d := range ports {
+		port, err := fs.parseInfiniBandPort(name, d.Name())
+		if err != nil {
+			return nil, err
+		}
+
+		device.Ports[port.Port] = *port
+	}
+
+	return &device, nil
+}
+
+// Parse InfiniBand state. Expected format: "<id>: <string-representation>"
+func parseState(s string) (uint, string, error) {
+	parts := strings.Split(s, ":")
+	if len(parts) != 2 {
+		return 0, "", fmt.Errorf("failed to split %s into 'ID: NAME'", s)
+	}
+	name := strings.TrimSpace(parts[1])
+	value, err := strconv.ParseUint(strings.TrimSpace(parts[0]), 10, 32)
+	if err != nil {
+		return 0, name, fmt.Errorf("failed to convert %s into uint", strings.TrimSpace(parts[0]))
+	}
+	id := uint(value)
+	return id, name, nil
+}
+
+// Parse rate (example: "100 Gb/sec (4X EDR)") and return it as bytes/second
+func parseRate(s string) (uint64, error) {
+	parts := strings.Split(s, "Gb/sec")
+	if len(parts) != 2 {
+		return 0, fmt.Errorf("failed to split '%s' by 'Gb/sec'", s)
+	}
+	value, err := strconv.ParseFloat(strings.TrimSpace(parts[0]), 32)
+	if err != nil {
+		return 0, fmt.Errorf("failed to convert %s into uint", strings.TrimSpace(parts[0]))
+	}
+	// Convert Gb/s into bytes/s
+	rate := uint64(value * 125000000)
+	return rate, nil
+}
+
+// parseInfiniBandPort scans predefined files in /sys/class/infiniband/<device>/ports/<port>
+// directory and gets their contents.
+func (fs FS) parseInfiniBandPort(name string, port string) (*InfiniBandPort, error) {
+	portNumber, err := strconv.ParseUint(port, 10, 32)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert %s into uint", port)
+	}
+	ibp := InfiniBandPort{Name: name, Port: uint(portNumber)}
+
+	portPath := fs.sys.Path(infinibandClassPath, name, "ports", port)
+	content, err := ioutil.ReadFile(filepath.Join(portPath, "state"))
+	if err != nil {
+		return nil, err
+	}
+	id, name, err := parseState(string(content))
+	if err != nil {
+		return nil, fmt.Errorf("could not parse state file in %s: %s", portPath, err)
+	}
+	ibp.State = name
+	ibp.StateID = id
+
+	content, err = ioutil.ReadFile(filepath.Join(portPath, "phys_state"))
+	if err != nil {
+		return nil, err
+	}
+	id, name, err = parseState(string(content))
+	if err != nil {
+		return nil, fmt.Errorf("could not parse phys_state file in %s: %s", portPath, err)
+	}
+	ibp.PhysState = name
+	ibp.PhysStateID = id
+
+	content, err = ioutil.ReadFile(filepath.Join(portPath, "rate"))
+	if err != nil {
+		return nil, err
+	}
+	ibp.Rate, err = parseRate(string(content))
+	if err != nil {
+		return nil, fmt.Errorf("could not parse rate file in %s: %s", portPath, err)
+	}
+
+	counters, err := parseInfiniBandCounters(portPath)
+	if err != nil {
+		return nil, err
+	}
+	ibp.Counters = *counters
+
+	return &ibp, nil
+}
+
+func parseInfiniBandCounters(portPath string) (*InfiniBandCounters, error) {
+	var counters InfiniBandCounters
+
+	path := filepath.Join(portPath, "counters")
+	files, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, f := range files {
+		if f.IsDir() {
+			continue
+		}
+
+		name := filepath.Join(path, f.Name())
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
+		}
+
+		// According to Mellanox, the metrics port_rcv_data, port_xmit_data,
+		// port_rcv_data_64, and port_xmit_data_64 "are divided by 4 unconditionally"
+		// as they represent the amount of data being transmitted and received per lane.
+		// Mellanox cards have 4 lanes per port, so all values must be multiplied by 4
+		// to get the expected value.
+
+		vp := util.NewValueParser(value)
+
+		switch f.Name() {
+		case "link_downed":
+			counters.LinkDowned = vp.PUInt64()
+		case "link_error_recovery":
+			counters.LinkErrorRecovery = vp.PUInt64()
+		case "multicast_rcv_packets":
+			counters.MulticastRcvPackets = vp.PUInt64()
+		case "multicast_xmit_packets":
+			counters.MulticastXmitPackets = vp.PUInt64()
+		case "port_rcv_constraint_errors":
+			counters.PortRcvConstraintErrors = vp.PUInt64()
+		case "port_rcv_data":
+			counters.PortRcvData = vp.PUInt64()
+			*counters.PortRcvData *= 4
+		case "port_rcv_discards":
+			counters.PortRcvDiscards = vp.PUInt64()
+		case "port_rcv_errors":
+			counters.PortRcvErrors = vp.PUInt64()
+		case "port_rcv_packets":
+			counters.PortRcvPackets = vp.PUInt64()
+		case "port_xmit_constraint_errors":
+			counters.PortXmitConstraintErrors = vp.PUInt64()
+		case "port_xmit_data":
+			counters.PortXmitData = vp.PUInt64()
+			*counters.PortXmitData *= 4
+		case "port_xmit_discards":
+			counters.PortXmitDiscards = vp.PUInt64()
+		case "port_xmit_packets":
+			counters.PortXmitPackets = vp.PUInt64()
+		case "port_xmit_wait":
+			counters.PortXmitWait = vp.PUInt64()
+		case "unicast_rcv_packets":
+			counters.UnicastRcvPackets = vp.PUInt64()
+		case "unicast_xmit_packets":
+			counters.UnicastXmitPackets = vp.PUInt64()
+		}
+
+		if err := vp.Err(); err != nil {
+			// Ugly workaround for handling https://github.com/prometheus/node_exporter/issues/966
+			// when counters are `N/A (not available)`.
+			// This was already patched and submitted, see
+			// https://www.spinics.net/lists/linux-rdma/msg68596.html
+			// Remove this as soon as the fix lands in the enterprise distros.
+			if strings.Contains(value, "N/A (no PMA)") {
+				continue
+			}
+			return nil, err
+		}
+	}
+
+	// Parse legacy counters
+	path = filepath.Join(portPath, "counters_ext")
+	files, err = ioutil.ReadDir(path)
+	if err != nil && !os.IsNotExist(err) {
+		return nil, err
+	}
+
+	for _, f := range files {
+		if f.IsDir() {
+			continue
+		}
+
+		name := filepath.Join(path, f.Name())
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
+		}
+
+		vp := util.NewValueParser(value)
+
+		switch f.Name() {
+		case "port_multicast_rcv_packets":
+			counters.LegacyPortMulticastRcvPackets = vp.PUInt64()
+		case "port_multicast_xmit_packets":
+			counters.LegacyPortMulticastXmitPackets = vp.PUInt64()
+		case "port_rcv_data_64":
+			counters.LegacyPortRcvData64 = vp.PUInt64()
+			*counters.LegacyPortRcvData64 *= 4
+		case "port_rcv_packets_64":
+			counters.LegacyPortRcvPackets64 = vp.PUInt64()
+		case "port_unicast_rcv_packets":
+			counters.LegacyPortUnicastRcvPackets = vp.PUInt64()
+		case "port_unicast_xmit_packets":
+			counters.LegacyPortUnicastXmitPackets = vp.PUInt64()
+		case "port_xmit_data_64":
+			counters.LegacyPortXmitData64 = vp.PUInt64()
+			*counters.LegacyPortXmitData64 *= 4
+		case "port_xmit_packets_64":
+			counters.LegacyPortXmitPackets64 = vp.PUInt64()
+		}
+
+		if err := vp.Err(); err != nil {
+			// Ugly workaround for handling https://github.com/prometheus/node_exporter/issues/966
+			// when counters are `N/A (not available)`.
+			// This was already patched and submitted, see
+			// https://www.spinics.net/lists/linux-rdma/msg68596.html
+			// Remove this as soon as the fix lands in the enterprise distros.
+			if strings.Contains(value, "N/A (no PMA)") {
+				continue
+			}
+			return nil, err
+		}
+	}
+
+	return &counters, nil
+}


Test patch:
diff --git a/fs_test.go b/fs_test.go
index 6e57745..1764bd4 100644
--- a/fs_test.go
+++ b/fs_test.go
@@ -33,7 +33,7 @@ func TestNewFS(t *testing.T) {
 func getProcFixtures(t *testing.T) FS {
 	fs, err := NewFS(procTestFixtures)
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Creating pseudo fs from getProcFixtures failed at fixtures/proc with error: %s", err)
 	}
 	return fs
 }
diff --git a/internal/util/valueparser_test.go b/internal/util/valueparser_test.go
index ad94bb2..d95745a 100644
--- a/internal/util/valueparser_test.go
+++ b/internal/util/valueparser_test.go
@@ -62,6 +62,46 @@ func TestValueParser(t *testing.T) {
 				want := int64(255)
 				got := vp.PInt64()
 
+				if diff := cmp.Diff(&want, got); diff != "" {
+					t.Fatalf("unexpected integer (-want +got):\n%s", diff)
+				}
+			},
+		},
+		{
+			name: "bad PUInt64",
+			v:    "-42",
+			fn: func(_ *testing.T, vp *util.ValueParser) {
+				_ = vp.PUInt64()
+			},
+		},
+		{
+			name: "bad hex PUInt64",
+			v:    "0xhello",
+			fn: func(_ *testing.T, vp *util.ValueParser) {
+				_ = vp.PUInt64()
+			},
+		},
+		{
+			name: "ok PUInt64",
+			v:    "1",
+			ok:   true,
+			fn: func(t *testing.T, vp *util.ValueParser) {
+				want := uint64(1)
+				got := vp.PUInt64()
+
+				if diff := cmp.Diff(&want, got); diff != "" {
+					t.Fatalf("unexpected integer (-want +got):\n%s", diff)
+				}
+			},
+		},
+		{
+			name: "ok hex PUInt64",
+			v:    "0xff",
+			ok:   true,
+			fn: func(t *testing.T, vp *util.ValueParser) {
+				want := uint64(255)
+				got := vp.PUInt64()
+
 				if diff := cmp.Diff(&want, got); diff != "" {
 					t.Fatalf("unexpected integer (-want +got):\n%s", diff)
 				}
diff --git a/iscsi/get_test.go b/iscsi/get_test.go
new file mode 100644
index 0000000..fb69990
--- /dev/null
+++ b/iscsi/get_test.go
@@ -0,0 +1,212 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iscsi_test
+
+import (
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"github.com/prometheus/procfs/iscsi"
+)
+
+func TestGetStats(t *testing.T) {
+	tests := []struct {
+		invalid bool
+		stat    *iscsi.Stats
+	}{
+		{
+			stat: &iscsi.Stats{
+				Name: "iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0",
+				Tpgt: []iscsi.TPGT{
+					{
+						Name:     "tpgt_1",
+						TpgtPath: "../fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1",
+						IsEnable: true,
+						Luns: []iscsi.LUN{
+							{
+								Name:       "lun_0",
+								LunPath:    "../fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.8888bbbbddd0/tpgt_1/lun/lun_0",
+								Backstore:  "rd_mcp",
+								ObjectName: "ramdisk_lio_1G",
+								TypeNumber: "119",
+							},
+						},
+					},
+				},
+				RootPath: "../fixtures/sys/kernel/config/target/iscsi",
+			},
+		},
+		{
+			stat: &iscsi.Stats{
+				Name: "iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab",
+				Tpgt: []iscsi.TPGT{
+					{
+						Name:     "tpgt_1",
+						TpgtPath: "../fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1",
+						IsEnable: true,
+						Luns: []iscsi.LUN{
+							{
+								Name:       "lun_0",
+								LunPath:    "../fixtures/sys/kernel/config/target/iscsi/iqn.2003-01.org.linux-iscsi.osd1.x8664:sn.abcd1abcd2ab/tpgt_1/lun/lun_0",
+								Backstore:  "iblock",
+								ObjectName: "block_lio_rbd1",
+								TypeNumber: "0",
+							},
+						},
+					},
+				},
+				RootPath: "../fixtures/sys/kernel/config/target/iscsi",
+			},
+		},
+		{
+			stat: &iscsi.Stats{
+				Name: "iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0",
+				Tpgt: []iscsi.TPGT{
+					{
+						Name:     "tpgt_1",
+						TpgtPath: "../fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1",
+						IsEnable: true,
+						Luns: []iscsi.LUN{
+							{
+								Name:       "lun_0",
+								LunPath:    "../fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:dev.rbd0/tpgt_1/lun/lun_0",
+								Backstore:  "fileio",
+								ObjectName: "file_lio_1G",
+								TypeNumber: "1",
+							},
+						},
+					},
+				},
+				RootPath: "../fixtures/sys/kernel/config/target/iscsi",
+			},
+		},
+		{
+			stat: &iscsi.Stats{
+				Name: "iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo",
+				Tpgt: []iscsi.TPGT{
+					{
+						Name:     "tpgt_1",
+						TpgtPath: "../fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1",
+						IsEnable: true,
+						Luns: []iscsi.LUN{
+							{
+								Name:       "lun_0",
+								LunPath:    "../fixtures/sys/kernel/config/target/iscsi/iqn.2016-11.org.linux-iscsi.igw.x86:sn.ramdemo/tpgt_1/lun/lun_0",
+								Backstore:  "rbd",
+								ObjectName: "iscsi-images-demo",
+								TypeNumber: "0",
+							},
+						},
+					},
+				},
+				RootPath: "../fixtures/sys/kernel/config/target/iscsi",
+			},
+		},
+	}
+
+	readTests := []struct {
+		read  uint64
+		write uint64
+		iops  uint64
+	}{
+		{10325, 40325, 204950},
+		{20095, 71235, 104950},
+		{10195, 30195, 301950},
+		{1504, 4733, 1234},
+	}
+
+	sysfs, err := iscsi.NewFS("../fixtures/sys/kernel/config")
+	if err != nil {
+		t.Fatalf("failed to access xfs fs: %v", err)
+	}
+	sysfsStat, err := sysfs.ISCSIStats()
+	statSize := len(sysfsStat)
+	if statSize != 4 {
+		t.Errorf("fixtures size does not match %d", statSize)
+	}
+	if err != nil {
+		t.Errorf("unexpected test fixtures")
+	}
+
+	for i, stat := range sysfsStat {
+		want, have := tests[i].stat, stat
+		if !reflect.DeepEqual(want, have) {
+			t.Errorf("unexpected iSCSI stats:\nwant:\n%v\nhave:\n%v", want, have)
+		} else {
+			readMB, writeMB, iops, err := iscsi.ReadWriteOPS(stat.RootPath+"/"+stat.Name,
+				stat.Tpgt[0].Name, stat.Tpgt[0].Luns[0].Name)
+			if err != nil {
+				t.Errorf("unexpected iSCSI ReadWriteOPS path %s %s %s",
+					stat.Name, stat.Tpgt[0].Name, stat.Tpgt[0].Luns[0].Name)
+				t.Errorf("%v", err)
+			}
+			if !reflect.DeepEqual(readTests[i].read, readMB) {
+				t.Errorf("unexpected iSCSI read data :\nwant:\n%v\nhave:\n%v", readTests[i].read, readMB)
+			}
+			if !reflect.DeepEqual(readTests[i].write, writeMB) {
+				t.Errorf("unexpected iSCSI write data :\nwant:\n%v\nhave:\n%v", readTests[i].write, writeMB)
+			}
+			if !reflect.DeepEqual(readTests[i].iops, iops) {
+				t.Errorf("unexpected iSCSI iops data :\nwant:\n%v\nhave:\n%v", readTests[i].iops, iops)
+			}
+			if stat.Tpgt[0].Luns[0].Backstore == "rd_mcp" {
+				have_rdmcp := new(iscsi.RDMCP)
+				have_rdmcp, err := have_rdmcp.GetRDMCPPath(filepath.Join("../fixtures/sys/kernel/config", iscsi.TargetCore), "119", "ramdisk_lio_1G")
+				if err != nil {
+					t.Errorf("fail rdmcp error %v", err)
+				}
+				// Name ObjectName
+				want_rdmcp := &iscsi.RDMCP{"rd_mcp_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName}
+
+				if !reflect.DeepEqual(want_rdmcp, have_rdmcp) {
+					t.Errorf("unexpected rdmcp data :\nwant:\n%v\nhave:\n%v", want_rdmcp, have_rdmcp)
+				}
+			} else if stat.Tpgt[0].Luns[0].Backstore == "iblock" {
+				have_iblock := new(iscsi.IBLOCK)
+				have_iblock, err = have_iblock.GetIblockUdev(filepath.Join("../fixtures/sys/kernel/config", iscsi.TargetCore), "0", "block_lio_rbd1")
+				if err != nil {
+					t.Errorf("fail iblock error %v", err)
+				}
+				// Name Bnumber ObjectName Iblock
+				want_iblock := &iscsi.IBLOCK{"iblock_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].ObjectName, "/dev/rbd1"}
+				if !reflect.DeepEqual(want_iblock, have_iblock) {
+					t.Errorf("unexpected iblock data :\nwant:\n%v\nhave:\n%v", want_iblock, have_iblock)
+				}
+			} else if stat.Tpgt[0].Luns[0].Backstore == "fileio" {
+				have_fileio := new(iscsi.FILEIO)
+				have_fileio, err = have_fileio.GetFileioUdev(filepath.Join("../fixtures/sys/kernel/config", iscsi.TargetCore), "1", "file_lio_1G")
+				if err != nil {
+					t.Errorf("fail fileio error %v", err)
+				}
+				// Name, Fnumber, ObjectName, Filename
+				want_fileio := &iscsi.FILEIO{"fileio_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "file_lio_1G", "/home/iscsi/file_back_1G"}
+				if !reflect.DeepEqual(want_fileio, have_fileio) {
+					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", want_fileio, have_fileio)
+				}
+			} else if stat.Tpgt[0].Luns[0].Backstore == "rbd" {
+				have_rbd := new(iscsi.RBD)
+				have_rbd, err = have_rbd.GetRBDMatch("../fixtures/sys", "0", "iscsi-images-demo")
+				if err != nil {
+					t.Errorf("fail rbd error %v", err)
+				}
+				// Name, Rnumber, Pool, Image
+				want_rbd := &iscsi.RBD{"rbd_" + stat.Tpgt[0].Luns[0].TypeNumber, stat.Tpgt[0].Luns[0].TypeNumber, "iscsi-images", "demo"}
+				if !reflect.DeepEqual(want_rbd, have_rbd) {
+					t.Errorf("unexpected fileio data :\nwant:\n%v\nhave:\n%v", want_rbd, have_rbd)
+				}
+			}
+		}
+	}
+}
diff --git a/mdstat_test.go b/mdstat_test.go
index 99a5adf..f2d2648 100644
--- a/mdstat_test.go
+++ b/mdstat_test.go
@@ -13,24 +13,33 @@
 
 package procfs
 
-import (
-	"testing"
-)
+import "testing"
+
+func TestFS_MDStat(t *testing.T) {
+	fs := getProcFixtures(t)
+	mdStats, err := fs.MDStat()
 
-func TestMDStat(t *testing.T) {
-	mdStats, err := getProcFixtures(t).MDStat()
 	if err != nil {
 		t.Fatalf("parsing of reference-file failed entirely: %s", err)
 	}
 
 	refs := map[string]MDStat{
-		"md3":   {"md3", "active", 8, 8, 5853468288, 5853468288},
-		"md127": {"md127", "active", 2, 2, 312319552, 312319552},
-		"md0":   {"md0", "active", 2, 2, 248896, 248896},
-		"md4":   {"md4", "inactive", 2, 2, 4883648, 4883648},
-		"md6":   {"md6", "active", 1, 2, 195310144, 16775552},
-		"md8":   {"md8", "active", 2, 2, 195310144, 16775552},
-		"md7":   {"md7", "active", 3, 4, 7813735424, 7813735424},
+		"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},
 	}
 
 	if want, have := len(refs), len(mdStats); want != have {
@@ -41,4 +50,20 @@ func TestMDStat(t *testing.T) {
 			t.Errorf("%s: want %v, have %v", md.Name, want, have)
 		}
 	}
+
+}
+
+func TestInvalidMdstat(t *testing.T) {
+	invalidMount := []byte(`
+Personalities : [invalid]
+md3 : invalid
+      314159265 blocks 64k chunks
+
+unused devices: <none>
+`)
+
+	_, err := parseMDStat(invalidMount)
+	if err == nil {
+		t.Fatalf("parsing of invalid reference file did not find any errors")
+	}
 }
diff --git a/mountinfo_test.go b/mountinfo_test.go
new file mode 100644
index 0000000..3565979
--- /dev/null
+++ b/mountinfo_test.go
@@ -0,0 +1,135 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package procfs
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestMountInfo(t *testing.T) {
+	tests := []struct {
+		name    string
+		s       string
+		mount   *MountInfo
+		invalid bool
+	}{
+		{
+			name:    "Regular sysfs mounted at /sys",
+			s:       "16 21 0:16 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw",
+			invalid: false,
+			mount: &MountInfo{
+				MountId:        16,
+				ParentId:       21,
+				MajorMinorVer:  "0:16",
+				Root:           "/",
+				MountPoint:     "/sys",
+				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "noexec": "", "relatime": ""},
+				OptionalFields: map[string]string{"shared": "7"},
+				FSType:         "sysfs",
+				Source:         "sysfs",
+				SuperOptions:   map[string]string{"rw": ""},
+			},
+		},
+		{
+			name:    "Not enough information",
+			s:       "hello",
+			invalid: true,
+		},
+		{
+			name: "Tmpfs mounted at /run",
+			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
+			mount: &MountInfo{
+				MountId:        225,
+				ParentId:       20,
+				MajorMinorVer:  "0:39",
+				Root:           "/",
+				MountPoint:     "/run/user/112",
+				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
+				OptionalFields: map[string]string{"shared": "177"},
+				FSType:         "tmpfs",
+				Source:         "tmpfs",
+				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
+			},
+			invalid: false,
+		},
+		{
+			name: "Tmpfs mounted at /run, but no optional values",
+			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime  - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
+			mount: &MountInfo{
+				MountId:        225,
+				ParentId:       20,
+				MajorMinorVer:  "0:39",
+				Root:           "/",
+				MountPoint:     "/run/user/112",
+				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
+				OptionalFields: nil,
+				FSType:         "tmpfs",
+				Source:         "tmpfs",
+				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
+			},
+			invalid: false,
+		},
+		{
+			name: "Tmpfs mounted at /run, with multiple optional values",
+			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
+			mount: &MountInfo{
+				MountId:        225,
+				ParentId:       20,
+				MajorMinorVer:  "0:39",
+				Root:           "/",
+				MountPoint:     "/run/user/112",
+				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
+				OptionalFields: map[string]string{"shared": "177", "master": "8"},
+				FSType:         "tmpfs",
+				Source:         "tmpfs",
+				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
+			},
+			invalid: false,
+		},
+		{
+			name: "Tmpfs mounted at /run, with a mixture of valid and invalid optional values",
+			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 foo:bar - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
+			mount: &MountInfo{
+				MountId:        225,
+				ParentId:       20,
+				MajorMinorVer:  "0:39",
+				Root:           "/",
+				MountPoint:     "/run/user/112",
+				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
+				OptionalFields: map[string]string{"shared": "177", "master": "8"},
+				FSType:         "tmpfs",
+				Source:         "tmpfs",
+				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
+			},
+			invalid: false,
+		},
+	}
+
+	for i, test := range tests {
+		t.Logf("[%02d] test %q", i, test.name)
+
+		mount, err := parseMountInfoString(test.s)
+
+		if test.invalid && err == nil {
+			t.Error("expected an error, but none occurred")
+		}
+		if !test.invalid && err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+
+		if want, have := test.mount, mount; !reflect.DeepEqual(want, have) {
+			t.Errorf("mounts:\nwant:\n%+v\nhave:\n%+v", want, have)
+		}
+	}
+}
diff --git a/proc_environ_test.go b/proc_environ_test.go
new file mode 100644
index 0000000..235f888
--- /dev/null
+++ b/proc_environ_test.go
@@ -0,0 +1,47 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package procfs
+
+import "testing"
+
+func TestProcEnviron(t *testing.T) {
+	p, err := getProcFixtures(t).Proc(26231)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	environments, err := p.Environ()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedEnvironments := []string{
+		"PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+		"HOSTNAME=cd24e11f73a5",
+		"TERM=xterm",
+		"GOLANG_VERSION=1.12.5",
+		"GOPATH=/go",
+		"HOME=/root",
+	}
+
+	if want, have := len(expectedEnvironments), len(environments); want != have {
+		t.Errorf("want %d parsed environments, have %d", want, have)
+	}
+
+	for i, environment := range environments {
+		if want, have := expectedEnvironments[i], environment; want != have {
+			t.Errorf("%d: want %v, have %v", i, want, have)
+		}
+	}
+}
diff --git a/sysfs/class_infiniband_test.go b/sysfs/class_infiniband_test.go
new file mode 100644
index 0000000..857b5de
--- /dev/null
+++ b/sysfs/class_infiniband_test.go
@@ -0,0 +1,145 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestParseSlowRate(t *testing.T) {
+	tests := []struct {
+		rate string
+		want uint64
+	}{
+		{
+			rate: "2.5 Gb/sec (1X SDR)",
+			want: 312500000,
+		},
+		{
+			rate: "500 Gb/sec (4X HDR)",
+			want: 62500000000,
+		},
+	}
+
+	for _, tt := range tests {
+		rate, err := parseRate(tt.rate)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if rate != tt.want {
+			t.Errorf("Result for InfiniBand rate not correct: want %v, have %v", tt.want, rate)
+		}
+	}
+}
+
+func TestInfiniBandClass(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	got, err := fs.InfiniBandClass()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var (
+		port1LinkDowned               uint64
+		port1LinkErrorRecovery        uint64
+		port1PortRcvConstraintErrors  uint64
+		port1PortRcvData              uint64 = 8884894436
+		port1PortRcvErrors            uint64
+		port1PortRcvPackets           uint64 = 87169372
+		port1PortXmitConstraintErrors uint64
+		port1PortXmitData             uint64 = 106036453180
+		port1PortXmitDiscards         uint64
+		port1PortXmitPackets          uint64 = 85734114
+		port1PortXmitWait             uint64 = 3599
+
+		port2LinkDowned               uint64
+		port2LinkErrorRecovery        uint64
+		port2PortRcvConstraintErrors  uint64
+		port2PortRcvData              uint64 = 9841747136
+		port2PortRcvErrors            uint64
+		port2PortRcvPackets           uint64 = 89332064
+		port2PortXmitConstraintErrors uint64
+		port2PortXmitData             uint64 = 106161427560
+		port2PortXmitDiscards         uint64
+		port2PortXmitPackets          uint64 = 88622850
+		port2PortXmitWait             uint64 = 3846
+	)
+
+	want := InfiniBandClass{
+		"mlx4_0": InfiniBandDevice{
+			Name:            "mlx4_0",
+			BoardID:         "SM_1141000001000",
+			FirmwareVersion: "2.31.5050",
+			HCAType:         "MT4099",
+			Ports: map[uint]InfiniBandPort{
+				1: {
+					Name:        "mlx4_0",
+					Port:        1,
+					State:       "ACTIVE",
+					StateID:     4,
+					PhysState:   "LinkUp",
+					PhysStateID: 5,
+					Rate:        5000000000,
+					Counters: InfiniBandCounters{
+						LinkDowned:               &port1LinkDowned,
+						LinkErrorRecovery:        &port1LinkErrorRecovery,
+						PortRcvConstraintErrors:  &port1PortRcvConstraintErrors,
+						PortRcvData:              &port1PortRcvData,
+						PortRcvErrors:            &port1PortRcvErrors,
+						PortRcvPackets:           &port1PortRcvPackets,
+						PortXmitConstraintErrors: &port1PortXmitConstraintErrors,
+						PortXmitData:             &port1PortXmitData,
+						PortXmitDiscards:         &port1PortXmitDiscards,
+						PortXmitPackets:          &port1PortXmitPackets,
+						PortXmitWait:             &port1PortXmitWait,
+					},
+				},
+				2: {
+					Name:        "mlx4_0",
+					Port:        2,
+					State:       "ACTIVE",
+					StateID:     4,
+					PhysState:   "LinkUp",
+					PhysStateID: 5,
+					Rate:        5000000000,
+					Counters: InfiniBandCounters{
+						LinkDowned:               &port2LinkDowned,
+						LinkErrorRecovery:        &port2LinkErrorRecovery,
+						PortRcvConstraintErrors:  &port2PortRcvConstraintErrors,
+						PortRcvData:              &port2PortRcvData,
+						PortRcvErrors:            &port2PortRcvErrors,
+						PortRcvPackets:           &port2PortRcvPackets,
+						PortXmitConstraintErrors: &port2PortXmitConstraintErrors,
+						PortXmitData:             &port2PortXmitData,
+						PortXmitDiscards:         &port2PortXmitDiscards,
+						PortXmitPackets:          &port2PortXmitPackets,
+						PortXmitWait:             &port2PortXmitWait,
+					},
+				},
+			},
+		},
+	}
+
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Fatalf("unexpected InfiniBand class (-want +got):\n%s", diff)
+	}
+}